diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0e6cb52 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Traqueur + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..125d2fb --- /dev/null +++ b/README.md @@ -0,0 +1,192 @@ +# 🐝 MoreBees + +**A comprehensive Minecraft plugin that adds custom bee types, advanced beehive mechanics, and powerful bee management tools.** + +## ✨ Features + +### 🐝 Custom Bee Types +- **Multiple bee variants**: Redstone, Emerald, Diamond, Gold, Iron bees, and what you create! +- **Unique behaviors**: Each bee type has specific food preferences and flower requirements +- **Custom models**: Support for ModelEngine integration with custom 3D models +- **Breeding system**: Cross-breed different bee types to create new variants +- **Mutation mechanics**: Bees can mutate when flying over specific blocks + +### 🏠 Advanced Beehive System +- **Upgradeable beehives**: Three upgrade levels with different capacities and multipliers +- **Visual upgrade displays**: See your upgrades as 3D items on beehives +- **Custom honey production**: Each bee type produces unique honey and resources +- **Automatic resource conversion**: Honey can be converted to blocks and resources + +### 🛠️ Bee Management Tools +- **Bee Jar** 🍯: Capture and transport individual bees +- **Bee Box** 📦: Store up to 10 bees in a portable container +- **Easy release system**: Right-click to release bees, sneak+right-click to release all + +## 🔧 Installation + +1. **Download** the latest release from [GitHub Releases](https://github.com/Traqueur-Dev/MoreBees/releases) or [SpigotMC](). +2. **Place** the JAR file in your server's `plugins/` folder +3. **Restart** your server +4. **Configure** the plugin using the generated config files + +## 📋 Requirements + +- **Minecraft**: 1.21+ +- **Server Software**: Paper, Purpur, or other Paper-based servers +- **Java**: 17+ + +## 🔌 Soft Dependencies + +- **[ModelEngine](https://mythiccraft.io/index.php?resources/model-engine%E2%80%94ultimate-entity-model-manager-1-16-5-1-20-4.389/)**: For custom 3D bee models +- **[ItemsAdder](https://www.spigotmc.org/resources/itemsadder.73355/)**: Custom item integration +- **[Oraxen](https://www.spigotmc.org/resources/oraxen.72448/)**: Custom item integration +- **[Nexo](https://polymart.org/product/6901/nexo)**: Custom item integration + +## ⚙️ Configuration + +### Main Configuration (`config.yml`) + +```yaml +debug: true +fly-animation: flying # For ModelEngine, set this model in ModelEngine bees + +bees: + - type: redstone-bee + display-name: Redstone Bee + foods: [REDSTONE] + flowers: [REDSTONE_BLOCK] + product: REDSTONE_ORE + model: redstone-bee # Optional: ModelEngine model + # ... more bee types +``` + +### Breeding Configuration (`breeds.yml`) + +```yaml +breeds: + - parents: [redstone-bee, diamond-bee] + child: emerald-bee + chance: 1.0 + +mutations: + - parent: redstone-bee + child: emerald-bee + blocks: [REDSTONE_ORE] +``` + +### Upgrade Configuration (`upgrades.yml`) + +```yaml +upgrades: + - id: level-1 + max-bees: 3 + production-multiplier: 1.5 + produce-blocks: false + item: + material: COPPER_INGOT + name: Level 1 Upgrade +``` + +## 🎮 Commands + +| Command | Permission | Description | +|---------|------------|-------------| +| `/morebees` | `morebees.command.help` | Show help menu | +| `/morebees reload` | `morebees.command.reload` | Reload configuration | +| `/morebees egg [amount]` | `morebees.command.egg` | Give bee spawn eggs | +| `/morebees spawn [baby]` | `morebees.command.spawn` | Spawn a bee | +| `/morebees honey [amount]` | `morebees.command.honey` | Give honey items | +| `/morebees tool ` | `morebees.command.tool` | Give bee tools | +| `/morebees upgrade ` | `morebees.command.upgrade` | Give beehive upgrades | + +**Aliases**: `/bees`, `/mb`, `/bee` + +## 🎯 How to Use + +### Getting Started +1. **Spawn bees** using `/morebees egg ` +2. **Feed bees** with their preferred foods to breed them +3. **Place beehives** and let bees populate them +4. **Upgrade beehives** by right-clicking with upgrade items +5. **Harvest honey** using shears when beehives are full + +### Bee Management +- **Capture bees**: Right-click with a Bee Jar or Bee Box +- **Release bees**: Right-click to release, sneak+right-click to release all +- **Breed bees**: Feed two adult bees to make them breed +- **Create mutations**: Let bees with nectar fly over specific blocks + +### Beehive Upgrades +- **Apply upgrades**: Right-click beehive with upgrade item +- **Remove upgrades**: Sneak+right-click to retrieve upgrade +- **Visual feedback**: Upgrades appear as 3D items on beehives + +## 📘 Recipes Usage: How It Works + +All custom recipes in this plugin are powered by [**RecipesAPI**](https://github.com/Traqueur-Dev/RecipesAPI) — a lightweight and flexible library purpose-built to simplify and centralize the crafting system in Minecraft. + +This API was developed in-house to streamline how recipes are created, registered, and managed. It supports two ways to define recipes: + +* **YAML files** – perfect for server owners and developers who prefer clean, file-based configuration. +* **Java code** – ideal for dynamic recipe generation or advanced programmatic control. + +With **RecipesAPI**, you can: + +* Easily add, modify, or remove recipes without dealing with low-level Minecraft internals. +* Automatically validate crafting inputs and prevent invalid crafts. +* Use custom items, tags, and even advanced conditions in your recipes. + +🔍 For full documentation, visit the [**GitHub Wiki**](https://github.com/Traqueur-Dev/RecipesAPI/wiki). It includes everything you need — from YAML format examples to Java integration guides — to get started and make the most of RecipesAPI. + +## 🚀 Performance Optimization: Spiral Search Algorithm + +Our custom bee AI uses an optimized spiral search algorithm that reduces block scanning by up to **90%**. + +**[🎬 View Interactive Demo](https://traqueur-dev.github.io/MoreBees/)** + +### Key Benefits: +- ⚡ **10x faster** than traditional cube scanning +- 🎯 **Early termination** when target found +- 📈 **Scales perfectly** with multiple bees +- 🛡️ **TPS-friendly** for production servers + +## 🎨 ModelEngine Integration + +When ModelEngine is installed: +- **Custom 3D models** replace default bee textures +- **Animated bees** with flying animations +- **Automatic scaling** for baby bees + +## 🛠️ API for Developers + +MoreBees provides a comprehensive API for developers: + +```java +// Get the BeeManager +BeeManager beeManager = BeePlugin.getPlugin().getManager(BeeManager.class); +// or use the ServiceManager from spigot + +// Spawn a custom bee +beeManager.spawnBee(location, beeType, SpawnReason.CUSTOM, false, false); + +// Get bee type from entity +Optional beeType = beeManager.getBeeTypeFromEntity(bee); +``` + +### Debug Mode +Enable debug logging in `config.yml`: +```yaml +debug: true +``` + +## 📄 License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. + +## 🙏 Acknowledgments + +- **[RecipesAPI](https://github.com/Traqueur-Dev/RecipesAPI)** - Recipe management system +- **[Structura](https://github.com/Traqueur-Dev/Structura)** - Configuration framework +- **ModelEngine** - 3D model support + +**Made with ❤️ by [Traqueur_](https://github.com/Traqueur-Dev)** \ No newline at end of file diff --git a/api/build.gradle b/api/build.gradle index 0b24a4c..689166f 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -1,5 +1,9 @@ group 'api' +dependencies { + compileOnly("io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT") +} + shadowJar { destinationDirectory = rootProject.apiFolder archiveFileName = rootProject.name + "-API-" + rootProject.version + ".jar" diff --git a/api/src/main/java/fr/traqueur/morebees/BeePlugin.java b/api/src/main/java/fr/traqueur/morebees/BeePlugin.java deleted file mode 100644 index 3fa5d8c..0000000 --- a/api/src/main/java/fr/traqueur/morebees/BeePlugin.java +++ /dev/null @@ -1,6 +0,0 @@ -package fr.traqueur.morebees; - -import org.bukkit.plugin.java.JavaPlugin; - -public abstract class BeePlugin extends JavaPlugin { -} diff --git a/api/src/main/java/fr/traqueur/morebees/api/BeePlugin.java b/api/src/main/java/fr/traqueur/morebees/api/BeePlugin.java new file mode 100644 index 0000000..5266d08 --- /dev/null +++ b/api/src/main/java/fr/traqueur/morebees/api/BeePlugin.java @@ -0,0 +1,86 @@ +package fr.traqueur.morebees.api; + +import fr.traqueur.commands.spigot.CommandManager; +import fr.traqueur.morebees.api.settings.Settings; +import fr.traqueur.recipes.api.RecipesAPI; +import org.bukkit.event.Listener; +import org.bukkit.plugin.RegisteredServiceProvider; +import org.bukkit.plugin.ServicePriority; +import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.NotNull; + +import java.util.NoSuchElementException; + +/** + * The main class for the MoreBees plugin. + * This class provides methods to access various managers and settings. + * It also allows for registering listeners and managers. + */ +public abstract class BeePlugin extends JavaPlugin { + + /** + * Gets the command manager for this plugin. + * + * @return the command manager + */ + public abstract CommandManager<@NotNull BeePlugin> getCommandManager(); + + /** + * Retrieve a manager instance of the specified class. + * This method uses the Bukkit Services API to get the registered service provider for the specified class. + * If no provider is found, it throws a NoSuchElementException. + * @param clazz the class of the manager to retrieve + * @return the manager instance of the specified class + * @param the type of the manager + */ + public T getManager(Class clazz) { + RegisteredServiceProvider provider = getServer().getServicesManager().getRegistration(clazz); + if (provider == null) { + throw new NoSuchElementException("No provider found for " + clazz.getSimpleName() + " class."); + } + return provider.getProvider(); + } + + /** + * Registers a manager instance for the specified class. + * This method uses the Bukkit Services API to register the manager instance with normal service priority. + * @param clazz the class of the manager to register + * @param instance the instance of the manager to register + * @param the type of the manager interface + * @param the type of the manager implementation + */ + public void registerManager( Class clazz, T instance) { + getServer().getServicesManager().register(clazz, instance, this, ServicePriority.Normal); + Logger.debug("Registered manager for {} successfully", clazz.getSimpleName()); + } + + /** + * Registers a listener for this plugin. + * This method registers the listener with the plugin's server and logs the registration. + * + * @param listener the listener to register + */ + public void registerListener(Listener listener) { + getServer().getPluginManager().registerEvents(listener, this); + Logger.debug("Registered listener {} successfully", listener.getClass().getSimpleName()); + } + + /** + * Retrieves the settings of the specified class. + * This method uses the Bukkit Services API to get the registered service provider for the specified settings class. + * If no provider is found, it throws a NoSuchElementException. + * + * @param clazz the class of the settings to retrieve + * @return the settings instance of the specified class + * @param the type of the settings + */ + public abstract T getSettings(Class clazz); + + /** + * Retrieves the RecipesAPI instance for this plugin. + * This method uses the Bukkit Services API to get the registered service provider for RecipesAPI. + * + * @return the RecipesAPI instance + */ + public abstract RecipesAPI getRecipesAPI(); +} diff --git a/api/src/main/java/fr/traqueur/morebees/api/Logger.java b/api/src/main/java/fr/traqueur/morebees/api/Logger.java new file mode 100644 index 0000000..fd912a4 --- /dev/null +++ b/api/src/main/java/fr/traqueur/morebees/api/Logger.java @@ -0,0 +1,178 @@ +package fr.traqueur.morebees.api; + +import java.util.HashMap; +import java.util.Map; + +/** + * Logger class for MoreBees API. + * This class provides methods to log messages with different severity levels + * and supports ANSI color codes for console output. + */ +public class Logger { + + /** + * The logger instance used for logging messages. + * It should be initialized using the init method before use. + */ + private static org.slf4j.Logger LOGGER; + /** + * Debug mode flag. If true, debug messages will be logged. + * This should be set during initialization. + */ + private static boolean DEBUG = false; + + /** + * ANSI color codes mapping for converting MiniMessage tags to ANSI escape codes. + * This map is used to replace MiniMessage tags with their corresponding ANSI codes. + */ + private static final Map ANSI_COLORS = new HashMap<>(); + + static { + // Color tags to ANSI + ANSI_COLORS.put("", "\u001B[31m"); + ANSI_COLORS.put("", "\u001B[32m"); + ANSI_COLORS.put("", "\u001B[33m"); + ANSI_COLORS.put("", "\u001B[34m"); + ANSI_COLORS.put("", "\u001B[36m"); + ANSI_COLORS.put("", "\u001B[37m"); + ANSI_COLORS.put("", "\u001B[97m"); + ANSI_COLORS.put("", "\u001B[30m"); + ANSI_COLORS.put("", "\u001B[31m"); + ANSI_COLORS.put("", "\u001B[32m"); + ANSI_COLORS.put("", "\u001B[36m"); + ANSI_COLORS.put("", "\u001B[34m"); + ANSI_COLORS.put("", "\u001B[33m"); + ANSI_COLORS.put("", "\u001B[95m"); + ANSI_COLORS.put("", "\u001B[35m"); + + // Formatting + ANSI_COLORS.put("", "\u001B[1m"); + ANSI_COLORS.put("", "\u001B[3m"); + ANSI_COLORS.put("", "\u001B[4m"); + ANSI_COLORS.put("", "\u001B[9m"); + ANSI_COLORS.put("", "\u001B[0m"); + } + + /** + * Initializes the logger with the provided SLF4J logger instance and debug mode. + * + * @param logger The SLF4J logger instance to use for logging. + * @param debug If true, enables debug logging. + */ + public static void init(org.slf4j.Logger logger, boolean debug) { + LOGGER = logger; + DEBUG = debug; + } + + /** + * Logs a debug message if debug mode is enabled. + * @param message The message to log. + * @param args Optional arguments to format the message. + */ + public static void debug(String message, Object... args) { + if (DEBUG) { + info(message, args); + } + } + + /** + * Logs an informational message. + * + * @param message The message to log. + * @param args Optional arguments to format the message. + */ + public static void info(String message, Object... args) { + log(Level.INFO, message, args); + } + + /** + * Logs a success message, typically used to indicate successful operations. + * + * @param message The success message to log. + * @param args Optional arguments to format the message. + */ + public static void success(String message, Object... args) { + log(Level.INFO, "" + message + "", args); + } + + /** + * Logs a warning message, typically used to indicate potential issues. + * + * @param message The warning message to log. + * @param args Optional arguments to format the message. + */ + public static void warning(String message, Object... args) { + log(Level.WARN, "" + message + "", args); + } + + /** + * Logs an error message, typically used to indicate failures or critical issues. + * + * @param message The error message to log. + * @param args Optional arguments to format the message. + */ + public static void severe(String message, Object... args) { + log(Level.ERROR, "" + message + "", args); + } + + /** + * Logs an informational message with an exception. + * + * @param message The message to log. + * @param exception The exception to log. + * @param args Optional arguments to format the message. + */ + public static void severe(String message, Exception exception, Object... args) { + log("" + message + "", exception, args); + } + + private static void log(Level level, String message, Object... args) { + ensureInitialized(); + String formatted = convertMiniMessageToAnsi(message); + switch (level) { + case INFO -> LOGGER.info(formatted, args); + case WARN -> LOGGER.warn(formatted, args); + case ERROR -> LOGGER.error(formatted, args); + } + } + + private static void log(String message, Exception exception, Object... args) { + ensureInitialized(); + String formatted = convertMiniMessageToAnsi(message); + LOGGER.error(formatted, args, exception); + } + + /** + * Ensures that the logger is initialized before any logging operations. + * This method should be called before using any logging methods to prevent NullPointerExceptions. + */ + private static void ensureInitialized() { + if (LOGGER == null) { + throw new IllegalStateException("Logger is not initialized. Call Logger.init() first."); + } + } + + /** + * Converts a MiniMessage formatted string to an ANSI formatted string. + * This method replaces MiniMessage tags with their corresponding ANSI escape codes. + * + * @param message The MiniMessage formatted string. + * @return The ANSI formatted string. + */ + private static String convertMiniMessageToAnsi(String message) { + String ansiMessage = message; + for (Map.Entry entry : ANSI_COLORS.entrySet()) { + ansiMessage = ansiMessage.replace(entry.getKey(), entry.getValue()); + } + ansiMessage += ANSI_COLORS.get(""); + return ansiMessage; + } + + /** + * Enum representing the logging levels. + * This is used to differentiate between info, warning, and error messages. + */ + private enum Level { + INFO, WARN, ERROR + } +} diff --git a/api/src/main/java/fr/traqueur/morebees/api/Manager.java b/api/src/main/java/fr/traqueur/morebees/api/Manager.java new file mode 100644 index 0000000..16f50ef --- /dev/null +++ b/api/src/main/java/fr/traqueur/morebees/api/Manager.java @@ -0,0 +1,20 @@ +package fr.traqueur.morebees.api; + +/** + * Manager interface for the MoreBees API. + * This interface provides a method to access the main plugin instance. + * It is intended to be implemented by classes that manage or interact with the plugin. + */ +public interface Manager { + + /** + * Gets the main plugin instance. + * This method provides access to the main plugin instance of MoreBees. + * + * @return The main plugin instance. + */ + default BeePlugin getPlugin() { + return BeePlugin.getPlugin(BeePlugin.class); + } + +} diff --git a/api/src/main/java/fr/traqueur/morebees/api/Messages.java b/api/src/main/java/fr/traqueur/morebees/api/Messages.java new file mode 100644 index 0000000..fa3a1f9 --- /dev/null +++ b/api/src/main/java/fr/traqueur/morebees/api/Messages.java @@ -0,0 +1,82 @@ +package fr.traqueur.morebees.api; + +import fr.traqueur.morebees.api.util.Formatter; +import fr.traqueur.morebees.api.util.MiniMessageHelper; +import fr.traqueur.structura.api.Loadable; +import org.bukkit.command.CommandSender; + +/** + * Enum representing various messages used in the MoreBees plugin. + * Each message can be formatted and sent to a CommandSender. + */ +public enum Messages implements Loadable { + + COMMAND_HELP_TITLE("%plugin% %version% by %authors% - Commands List"), + COMMAND_SYNTAX("%usage% %description%"), + + RELOAD_COMMAND_DESC("Reloads the plugin configuration."), + RELOAD_SUCCESS("Configuration reloaded successfully!"), + + COMMAND_AMOUNT_INVALID("Invalid amount for %amount%! Please choose a number between 1 and %max-amount%."), + + EGG_COMMAND_DESC("Gives you a bee egg."), + EGG_COMMAND_SUCCESS("Successfully given %amount% %beetype% egg(s) to %player%!"), + + SPAWN_COMMAND_DESC("Spawn a bee at the player location."), + SPAWN_COMMAND_SUCCESS("Successfully spawned a %beetype% bee at your location!"), + + HONEY_COMMAND_DESC("Gives you honey from a bee type."), + HONEY_COMMAND_SUCCESS("Successfully given %amount% honey from %beetype% to %player%!"), + + EMPTY_BEE_JAR("Empty"), + EMPTY_BEE_BOX("Empty"), + BEE_JAR_CONTENT("%beetype%"), + BEE_BOX_CONTENT("%beetype% x%amount%"), + + TOOL_FULL("Your %tool% is already full!"), + + TOOL_COMMAND_DESC("Gives a tool to a player."), + TOOL_COMMAND_SUCCESS("Successfully given %tool% to %player%!"), + + PRODUCE_BLOCKS_NO("No"), + PRODUCE_BLOCKS_YES("Yes"), + + UPGRADE_COMMAND_DESC("Gives an upgrade to a player."), + UPGRADE_COMMAND_SUCCESS("Successfully given %upgrade% to %player%!"),; + + /** The raw message string for this enum constant. */ + private final String message; + + /** + * Constructs a Messages enum with the specified message. + * + * @param message The raw message string. + */ + Messages(String message) { + this.message = message; + } + + /** + * Returns the raw message string for this enum constant. + * + * @return The raw message string. + */ + public String raw() { + return this.message; + } + + /** + * Sends the formatted message to the specified CommandSender. + * + * @param sender The CommandSender to send the message to. + * @param formatters Optional formatters to apply to the message. + */ + public void send(CommandSender sender, Formatter... formatters) { + String formattedMessage = this.message; + for (Formatter formatter : formatters) { + formattedMessage = formatter.handle(BeePlugin.getPlugin(BeePlugin.class), formattedMessage); + } + sender.sendMessage(MiniMessageHelper.parse(formattedMessage)); + } + +} diff --git a/api/src/main/java/fr/traqueur/morebees/api/hooks/Hook.java b/api/src/main/java/fr/traqueur/morebees/api/hooks/Hook.java new file mode 100644 index 0000000..6f48d85 --- /dev/null +++ b/api/src/main/java/fr/traqueur/morebees/api/hooks/Hook.java @@ -0,0 +1,53 @@ +package fr.traqueur.morebees.api.hooks; + +import fr.traqueur.morebees.api.BeePlugin; + +import java.util.HashSet; +import java.util.Set; + +/** + * Represents a hook that can be enabled in the MoreBees plugin. + * Hooks are used to integrate with other plugins or systems. + */ +public interface Hook { + + /** + * A set of all registered hooks. + */ + Set HOOKS = new HashSet<>(); + + /** + * Get all hooks of a specific type. + * @param type the class of the hook type to retrieve + * @return a set of hooks of the specified type + * @param the type of the hook + */ + static Set getByClass(Class type) { + Set hooks = new HashSet<>(); + for (Hook hook : HOOKS) { + if (type.isInstance(hook)) { + hooks.add(type.cast(hook)); + } + } + return hooks; + } + + /** + * Register a new hook. + * This method is called to register a hook in the MoreBees plugin. + * + * @param hook the hook to register + */ + static void register(Hook hook) { + HOOKS.add(hook); + hook.onEnable(BeePlugin.getPlugin(BeePlugin.class)); + } + + /** + * Enable the hook for the specified plugin. + * This method is called when the plugin is enabled. + * + * @param plugin the plugin that is enabling the hook + */ + void onEnable(BeePlugin plugin); +} diff --git a/api/src/main/java/fr/traqueur/morebees/api/hooks/ItemProviderHook.java b/api/src/main/java/fr/traqueur/morebees/api/hooks/ItemProviderHook.java new file mode 100644 index 0000000..04446da --- /dev/null +++ b/api/src/main/java/fr/traqueur/morebees/api/hooks/ItemProviderHook.java @@ -0,0 +1,37 @@ +package fr.traqueur.morebees.api.hooks; + +import org.bukkit.block.Block; +import org.bukkit.inventory.ItemStack; + +import javax.annotation.Nullable; + +/** + * ItemProviderHook is a hook interface that allows plugins to provide custom item names and item stacks. + * This can be used to retrieve item names from ItemStacks or Blocks, and to create ItemStacks from product IDs. + */ +public interface ItemProviderHook extends Hook { + + /** + * Gets the name of the item from the given ItemStack. + * + * @param item The ItemStack to get the name from. + * @return The name of the item, or null if not available. + */ + @Nullable String getItemName(ItemStack item); + + /** + * Gets the name of the block from the given Block. + * + * @param block The Block to get the name from. + * @return The name of the block, or null if not available. + */ + @Nullable String getBlockName(Block block); + + /** + * Gets an ItemStack from the given ID. + * + * @param id The product ID to get the ItemStack from. + * @return The ItemStack corresponding to the product ID, or null if not available. + */ + ItemStack getItemFromId(String id); +} diff --git a/api/src/main/java/fr/traqueur/morebees/api/managers/BeeManager.java b/api/src/main/java/fr/traqueur/morebees/api/managers/BeeManager.java new file mode 100644 index 0000000..bf89c8a --- /dev/null +++ b/api/src/main/java/fr/traqueur/morebees/api/managers/BeeManager.java @@ -0,0 +1,85 @@ +package fr.traqueur.morebees.api.managers; + +import fr.traqueur.morebees.api.Manager; +import fr.traqueur.morebees.api.models.BeeType; +import fr.traqueur.morebees.api.models.Mutation; +import org.bukkit.Location; +import org.bukkit.entity.Bee; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; + +/** + * The BeeManager interface provides methods for managing bees in the MoreBees plugin. + * It allows for retrieving bee types from items or entities, spawning bees, patching bees, + * computing child types, feeding bees, and mutating them. + */ +public interface BeeManager extends Manager { + + /** + * Retrieves the BeeType from an ItemStack if it contains a bee egg. + * + * @param itemStack the ItemStack to check + * @return an Optional containing the BeeType if found, otherwise empty + */ + Optional getBeeTypeFromEgg(ItemStack itemStack); + + /** + * Retrieves the BeeType from a LivingEntity if it is a bee. + * + * @param entity the LivingEntity to check + * @return an Optional containing the BeeType if found, otherwise empty + */ + Optional getBeeTypeFromEntity(LivingEntity entity); + + /** + * Spawns a bee at the specified location with the given BeeType and spawn reason. + * + * @param location the location to spawn the bee + * @param beeType the type of bee to spawn + * @param reason the reason for spawning the bee + * @param baby whether the bee should be a baby + * @param nectar whether the bee should have nectar + */ + void spawnBee(Location location, @NotNull BeeType beeType, CreatureSpawnEvent.SpawnReason reason, boolean baby, boolean nectar); + + /** + * Patches a bee with the specified BeeType. + *

+ * Patch a bee to apply all goals and properties of the BeeType to the bee. + *

+ * @param bee the bee to patch + * @param beeType the BeeType to apply to the bee + */ + void patchBee(Bee bee, @NotNull BeeType beeType); + + /** + * Computes the child type of bee based on its parents' types. + * + * @param mother the BeeType of the mother + * @param father the BeeType of the father + * @return the computed child BeeType + */ + @NotNull BeeType computeChildType(@NotNull BeeType mother, @NotNull BeeType father); + + /** + * Feeds a bee by a player, which may trigger certain behaviors or effects. + * + * @param player the player feeding the bee + * @param bee the bee to be fed + */ + void feed(@NotNull Player player, Bee bee); + + /** + * Mutates a bee to a new location with the specified mutation. + * + * @param bee the bee to mutate + * @param mutation the mutation to apply + * @param location the location to mutate the bee to + */ + void mutate(Bee bee, Mutation mutation, Location location); +} diff --git a/api/src/main/java/fr/traqueur/morebees/api/managers/BeehiveManager.java b/api/src/main/java/fr/traqueur/morebees/api/managers/BeehiveManager.java new file mode 100644 index 0000000..738b7bc --- /dev/null +++ b/api/src/main/java/fr/traqueur/morebees/api/managers/BeehiveManager.java @@ -0,0 +1,53 @@ +package fr.traqueur.morebees.api.managers; + +import fr.traqueur.morebees.api.Manager; +import fr.traqueur.morebees.api.models.Beehive; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.inventory.ItemStack; + +import java.util.Optional; +import java.util.function.Consumer; + +/** + * Manages beehives in the MoreBees API. + * Provides methods to retrieve, save, and edit beehives from blocks and items. + */ +public interface BeehiveManager extends Manager { + + /** + * Retrieves a Beehive from a BlockState. + * + * @param block The BlockState to retrieve the Beehive from. + * @return An Optional containing the Beehive if present, otherwise empty. + */ + Optional getBeehiveFromBlock(BlockState block); + + /** + * Retrieves a Beehive from an ItemStack. + * + * @param stack The ItemStack to retrieve the Beehive from. + * @return An Optional containing the Beehive if present, otherwise empty. + */ + Optional getBeehiveFromItem(ItemStack stack); + + /** + * Saves a Beehive to a Block. + * + * @param block The Block to save the Beehive to. The block must be a valid beehive block. + * If the block is not a valid beehive block, nothing will happen. + * It is recommended to check if the block is a beehive using {@link org.bukkit.block.Beehive}. + * @param beehive The Beehive to save. + */ + void saveBeehiveToBlock(Block block, Beehive beehive); + + /** + * Edits a Beehive from a Block. + * This method calls {@link fr.traqueur.morebees.api.managers.BeehiveManager#getBeehiveFromBlock} and then applies the provided Consumer to the Beehive and saves it back to the block with {@link fr.traqueur.morebees.api.managers.BeehiveManager#saveBeehiveToBlock}. + * @param block The Block to edit the Beehive from. The block must be a valid beehive block. + * If the block is not a valid beehive block, nothing will happen. + * It is recommended to check if the block is a beehive using {@link org.bukkit.block.Beehive}. + * @param consumer A Consumer that takes the Beehive and allows modifications. + */ + void editBeehive(Block block, Consumer consumer); +} diff --git a/api/src/main/java/fr/traqueur/morebees/api/managers/ToolsManager.java b/api/src/main/java/fr/traqueur/morebees/api/managers/ToolsManager.java new file mode 100644 index 0000000..1a2e981 --- /dev/null +++ b/api/src/main/java/fr/traqueur/morebees/api/managers/ToolsManager.java @@ -0,0 +1,63 @@ +package fr.traqueur.morebees.api.managers; + +import fr.traqueur.morebees.api.Manager; +import fr.traqueur.morebees.api.models.BeeData; +import fr.traqueur.morebees.api.models.BeeType; +import fr.traqueur.morebees.api.models.Tool; +import org.bukkit.entity.Bee; +import org.bukkit.inventory.ItemStack; + +import java.util.List; +import java.util.Optional; + +/** + * ToolsManager is responsible for managing tools related to bees. + * It provides methods to get tools, check if a tool is full, and manage bee data. + */ +public interface ToolsManager extends Manager { + + /** + * Gets the tool associated with the given ItemStack. + * + * @param itemStack the ItemStack to check + * @return an Optional containing the Tool if present, otherwise empty + */ + Optional getTool(ItemStack itemStack); + + /** + * Checks if the given ItemStack is a full tool. + * + * @param itemStack the ItemStack to check + * @return true if the tool is full, false otherwise + */ + boolean isFull(ItemStack itemStack); + + /** + * Converts a Bee and BeeType into a BeeData object. + * + * @param bee the Bee to convert + * @param beeType the type of the Bee + * @return a BeeData object representing the Bee and its type + */ + BeeData toData(Bee bee, BeeType beeType); + + /** + * Catches a Bee using the specified tool. + * + * @param tool the ItemStack representing the tool + * @param bee the Bee to catch + * @param beeType the type of the Bee + */ + void catchBee(ItemStack tool, Bee bee, BeeType beeType); + + /** + * Releases bees from the specified tool. + * + * @param tool the ItemStack representing the tool + * @param all if true, releases all bees; otherwise, releases only one + * @return a list of BeeData representing the released bees + * You must spawn the bees manually using the content of BeeData and the {@link fr.traqueur.morebees.api.managers.BeeManager#spawnBee} + */ + List releaseBee(ItemStack tool, boolean all); + +} diff --git a/api/src/main/java/fr/traqueur/morebees/api/managers/UpgradesManager.java b/api/src/main/java/fr/traqueur/morebees/api/managers/UpgradesManager.java new file mode 100644 index 0000000..48c92dc --- /dev/null +++ b/api/src/main/java/fr/traqueur/morebees/api/managers/UpgradesManager.java @@ -0,0 +1,53 @@ +package fr.traqueur.morebees.api.managers; + +import fr.traqueur.morebees.api.Manager; +import fr.traqueur.morebees.api.models.Upgrade; +import org.bukkit.block.Beehive; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.entity.ItemDisplay; +import org.bukkit.inventory.ItemStack; + +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Stream; + +/** + * Manages upgrades for beehives. + * Provides methods to retrieve upgrades from items, create displays for upgrades, + * and load beehive states with associated entities. + */ +public interface UpgradesManager extends Manager { + + /** + * Retrieves an upgrade from the given item stack. + * + * @param itemStack the item stack to check for an upgrade + * @return an Optional containing the Upgrade if found, otherwise empty + */ + Optional getUpgradeFromItem(ItemStack itemStack); + + /** + * Creates an ItemDisplay for the given upgrade on the specified beehive block. + * + * @param beehiveBlock the block representing the beehive + * @param upgrade the upgrade to display + * @return an ItemDisplay representing the upgrade + */ + ItemDisplay createUpgradeDisplay(Block beehiveBlock, Upgrade upgrade); + + /** + * Removes the upgrade display associated with the given UUID. + * + * @param displayUUID the UUID of the ItemDisplay to remove + */ + void removeUpgradeDisplay(UUID displayUUID); + + /** + * Loads the beehive state and associates it with the given entities. + * + * @param beehiveState the Beehive state to load + * @param entities a stream of entities to associate with the beehive + */ + void loadBeehive(Beehive beehiveState, Stream entities); +} diff --git a/api/src/main/java/fr/traqueur/morebees/api/models/BeeData.java b/api/src/main/java/fr/traqueur/morebees/api/models/BeeData.java new file mode 100644 index 0000000..3e208c7 --- /dev/null +++ b/api/src/main/java/fr/traqueur/morebees/api/models/BeeData.java @@ -0,0 +1,30 @@ +package fr.traqueur.morebees.api.models; + +/** + * Represents the data of a bee, including its type, nectar status, and age. + * This interface is used to encapsulate the properties of a bee in the MoreBees API. + */ +public interface BeeData { + + /** + * Gets the type of the bee. + * + * @return the type of the bee + */ + BeeType type(); + + /** + * Checks if the bee has nectar. + * + * @return true if the bee has nectar, false otherwise + */ + boolean hasNectar(); + + /** + * Checks if the bee is an adult. + * + * @return true if the bee is an adult, false otherwise + */ + boolean isAdult(); + +} diff --git a/api/src/main/java/fr/traqueur/morebees/api/models/BeeType.java b/api/src/main/java/fr/traqueur/morebees/api/models/BeeType.java new file mode 100644 index 0000000..3e79f7e --- /dev/null +++ b/api/src/main/java/fr/traqueur/morebees/api/models/BeeType.java @@ -0,0 +1,108 @@ +package fr.traqueur.morebees.api.models; + +import fr.traqueur.morebees.api.Logger; +import fr.traqueur.morebees.api.hooks.Hook; +import fr.traqueur.morebees.api.hooks.ItemProviderHook; +import fr.traqueur.morebees.api.serialization.Keys; +import fr.traqueur.morebees.api.serialization.datas.BeeTypeDataType; +import fr.traqueur.morebees.api.util.MiniMessageHelper; +import fr.traqueur.morebees.api.util.Util; +import fr.traqueur.structura.annotations.Options; +import fr.traqueur.structura.api.Loadable; +import org.bukkit.Material; +import org.bukkit.Tag; +import org.bukkit.block.Block; +import org.bukkit.inventory.ItemStack; +import org.bukkit.persistence.PersistentDataContainer; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Objects; +import java.util.Set; + +public record BeeType(String type, + @Options(optional = true) Integer modelId, + String displayName, + List foods, + List flowers, + String product, + @Options(optional = true) String model) implements Loadable { + + + public static final BeeType NORMAL = new BeeType("normal", null, "Bee", List.of(), List.of(), "", null); + + public BeeType { + if(foods.isEmpty() && !type.equalsIgnoreCase("normal")) { + Logger.warning("No foods defined for the bee type {}", type); + } + if(flowers.isEmpty() && !type.equalsIgnoreCase("normal")) { + Logger.warning("No flowers defined for the bee type {}", type); + } + } + + public @NotNull ItemStack productItem() { + if(product == null || product.isEmpty()) { + Logger.warning("No product defined for the bee type {}", type); + return ItemStack.of(Material.AIR); + } + + return Util.getItemFromId(product); + } + + public @NotNull ItemStack egg() { + ItemStack item = ItemStack.of(Material.BEE_SPAWN_EGG); + if(this.equals(NORMAL)) { + return item; + } + + item.editMeta(meta -> { + meta.itemName(MiniMessageHelper.parse(displayName + " Egg")); + PersistentDataContainer container = meta.getPersistentDataContainer(); + Keys.BEE_TYPE.set(container, BeeTypeDataType.INSTANCE, this); + if(modelId != null && modelId > 0) { + meta.setCustomModelData(modelId); + } + }); + return item; + } + + public @NotNull ItemStack honey(int amount, boolean block) { + + Material material = block ? Material.HONEYCOMB_BLOCK : Material.HONEYCOMB; + ItemStack item = ItemStack.of(material, amount); + + if(this.equals(NORMAL)) { + return item; + } + + item.editMeta(meta -> { + meta.itemName(MiniMessageHelper.parse(displayName + " Honey")); + PersistentDataContainer container = meta.getPersistentDataContainer(); + Keys.BEE_TYPE.set(container, BeeTypeDataType.INSTANCE, this); + if(modelId != null && modelId > 0) { + meta.setCustomModelData(modelId); + } + }); + return item; + } + + public boolean isFood(@NotNull ItemStack item) { + Material type = item.getType(); + + if(this.equals(NORMAL)) { + return Tag.FLOWERS.isTagged(type); + } + + Set hooks = Hook.getByClass(ItemProviderHook.class); + String itemName = hooks.stream().map(hook -> hook.getItemName(item)).filter(Objects::nonNull).findFirst().orElse(type.name()); + return foods.contains(itemName); + } + + public boolean isFlower(Block block) { + if(this.equals(NORMAL)) { + return Tag.FLOWERS.isTagged(block.getType()); + } + + return Util.isValidBlock(block, flowers); + } +} diff --git a/api/src/main/java/fr/traqueur/morebees/api/models/Beehive.java b/api/src/main/java/fr/traqueur/morebees/api/models/Beehive.java new file mode 100644 index 0000000..84c99e2 --- /dev/null +++ b/api/src/main/java/fr/traqueur/morebees/api/models/Beehive.java @@ -0,0 +1,76 @@ +package fr.traqueur.morebees.api.models; + +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +import javax.annotation.Nullable; +import java.util.Map; +import java.util.UUID; + +/** + * Represents a beehive in the MoreBees API. + * This interface provides methods to manage upgrades, honey comb counts, and honey operations. + */ +public interface Beehive { + + /** + * Gets the upgrade associated with this beehive. + * + * @return the upgrade + */ + @NotNull Upgrade getUpgrade(); + + /** + * Sets the upgrade for this beehive. + * + * @param upgrade the upgrade to set + */ + void setUpgrade(@NotNull Upgrade upgrade); + + /** + * Gets the unique identifier of the item display for the upgrade. + * + * @return the UUID of the upgrade + */ + @Nullable UUID getUpgradeId(); + + /** + * Sets the unique identifier of the item display for the upgrade. + * + * @param upgradeId the UUID to set + */ + void setUpgradeId(@Nullable UUID upgradeId); + + /** + * Gets the count of honey combs for each bee type in this beehive. + * + * @return a map of bee types to their respective honey comb counts + */ + Map getHoneyCombCounts(); + + /** + * Gets the count of honey combs for a specific bee type. + * + * @param beeType the type of bee + * @return the count of honey combs for the specified bee type + */ + int getHoneyCombCount(@NotNull BeeType beeType); + + /** + * Adds honey combs for a specific bee type. + * + * @param beeType the type of bee + * @param count the number of honey combs to add + */ + void addHoney(@NotNull BeeType beeType, int count); + + /** + * Removes honey combs for a specific bee type. + * + * @param beeType the type of bee + * @param count the number of honey combs to remove + */ + void removeHoney(@NotNull BeeType beeType, int count); + + @NotNull ItemStack patch(@NotNull ItemStack item); +} diff --git a/api/src/main/java/fr/traqueur/morebees/api/models/Breed.java b/api/src/main/java/fr/traqueur/morebees/api/models/Breed.java new file mode 100644 index 0000000..671dd6c --- /dev/null +++ b/api/src/main/java/fr/traqueur/morebees/api/models/Breed.java @@ -0,0 +1,32 @@ +package fr.traqueur.morebees.api.models; + +import fr.traqueur.morebees.api.BeePlugin; +import fr.traqueur.morebees.api.Logger; +import fr.traqueur.morebees.api.settings.GlobalSettings; +import fr.traqueur.structura.api.Loadable; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public record Breed(List parents, String child, double chance) implements Loadable { + + public Breed { + if (parents.size() != 2) { + throw new IllegalArgumentException("A breed must have exactly two parents."); + } + if (child == null || child.isEmpty()) { + throw new IllegalArgumentException("Child cannot be null or empty."); + } + if (chance < 0 || chance > 1) { + throw new IllegalArgumentException("Chance must be between 0 and 1."); + } + + Set types = new HashSet<>(parents); + types.add(child); + + if (!BeePlugin.getPlugin(BeePlugin.class).getSettings(GlobalSettings.class).contains(types.toArray(String[]::new))) { + Logger.warning("Some bee types in breed {} are not defined in settings: {}", child, types); + } + } +} diff --git a/api/src/main/java/fr/traqueur/morebees/api/models/Mutation.java b/api/src/main/java/fr/traqueur/morebees/api/models/Mutation.java new file mode 100644 index 0000000..1de2f6a --- /dev/null +++ b/api/src/main/java/fr/traqueur/morebees/api/models/Mutation.java @@ -0,0 +1,33 @@ +package fr.traqueur.morebees.api.models; + +import fr.traqueur.morebees.api.BeePlugin; +import fr.traqueur.morebees.api.Logger; +import fr.traqueur.morebees.api.settings.GlobalSettings; +import fr.traqueur.morebees.api.util.Util; +import fr.traqueur.structura.api.Loadable; +import org.bukkit.block.Block; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public record Mutation(String parent, String child, List blocks) implements Loadable { + + public Mutation { + if (blocks.isEmpty()) { + Logger.warning("Mutation with empty blocks: {} -> {}", parent, child); + } + Set types = new HashSet<>(); + types.add(child); + types.add(parent); + + if (!BeePlugin.getPlugin(BeePlugin.class).getSettings(GlobalSettings.class).contains(types.toArray(String[]::new))) { + Logger.warning("Some bee types in mutation {} -> {} are not defined in settings: {}", parent, child); + } + } + + public boolean canMutate(Block block) { + return Util.isValidBlock(block, blocks); + } + +} diff --git a/api/src/main/java/fr/traqueur/morebees/api/models/Tool.java b/api/src/main/java/fr/traqueur/morebees/api/models/Tool.java new file mode 100644 index 0000000..02dce42 --- /dev/null +++ b/api/src/main/java/fr/traqueur/morebees/api/models/Tool.java @@ -0,0 +1,110 @@ +package fr.traqueur.morebees.api.models; + +import fr.traqueur.morebees.api.BeePlugin; +import fr.traqueur.morebees.api.Messages; +import fr.traqueur.morebees.api.serialization.Keys; +import fr.traqueur.morebees.api.serialization.datas.ToolDataType; +import fr.traqueur.morebees.api.settings.GlobalSettings; +import fr.traqueur.morebees.api.settings.ItemStackWrapper; +import fr.traqueur.morebees.api.util.Formatter; +import fr.traqueur.morebees.api.util.Util; +import net.kyori.adventure.text.Component; +import org.bukkit.inventory.ItemStack; +import org.bukkit.persistence.PersistentDataContainer; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.BiFunction; +import java.util.function.Supplier; + +public enum Tool { + BEE_BOX(() -> BeePlugin.getPlugin(BeePlugin.class).getSettings(GlobalSettings.class).beeBoxSize(), + () -> BeePlugin.getPlugin(BeePlugin.class).getSettings(GlobalSettings.class).beeBox(), + "bees", + (placeholder, bees) -> { + String template = Messages.BEE_BOX_CONTENT.raw(); + Map beeCount = new HashMap<>(); + + boolean find = false; + for (BeeData bee : bees) { + BeeType type = bee.type(); + for (Map.Entry beeTypeLongEntry : beeCount.entrySet()) { + BeeType key = beeTypeLongEntry.getKey(); + if(key.type().equals(type.type())) { + beeCount.merge(key, 1L, Long::sum); + find = true; + break; + } + } + + if(!find) { + beeCount.put(type, 1L); + } + find = false; + } + + StringBuilder builder = new StringBuilder(); + beeCount.forEach((type, count) -> { + if (!builder.isEmpty()) { + builder.append("\n"); + } + builder.append(Formatter.format(template, Formatter.all("beetype", type.displayName(), "amount", count))); + }); + + if (builder.isEmpty()) { + builder.append(Messages.EMPTY_BEE_BOX.raw()); + } + + return Formatter.all(placeholder, builder.toString()); + }), + + BEE_JAR(() -> 1, + () -> BeePlugin.getPlugin(BeePlugin.class).getSettings(GlobalSettings.class).beeJar(), + "bee", + (placeholder, bees) -> { + String template = Messages.BEE_JAR_CONTENT.raw(); + + if(bees.isEmpty()) + return Formatter.all(placeholder, Messages.EMPTY_BEE_JAR.raw()); + + String row = Formatter.format(template, Formatter.all("beetype", bees.stream().map(BeeData::type).toList().getFirst().displayName())); + + return Formatter.all(placeholder, row); + }); + + private final Supplier maxBees; + private final Supplier itemStackSupplier; + private final BiFunction, Formatter[]> formatters; + private final String placeholder; + + Tool(Supplier maxBees, Supplier itemStackSupplier, String placeholder, BiFunction, Formatter[]> formatters) { + this.maxBees = maxBees; + this.itemStackSupplier = itemStackSupplier; + this.placeholder = placeholder; + this.formatters = formatters; + } + + public int maxBees() { + return this.maxBees.get(); + } + + public @NotNull ItemStack itemStack(List bees) { + ItemStackWrapper itemStackWrapper = itemStackSupplier.get(); + ItemStack itemStack = itemStackWrapper.build(this.formatters.apply(this.placeholder, bees)); + itemStack.editMeta(meta -> { + PersistentDataContainer container = meta.getPersistentDataContainer(); + Keys.TOOL_ID.set(container, ToolDataType.INSTANCE, this); + }); + return itemStack; + } + + public @NotNull List lore(List bees) { + List lore = this.itemStackSupplier.get().lore(); + if(lore == null) { + return List.of(); + } + return Util.parseLore(lore, this.formatters.apply(this.placeholder, bees)); + } + } \ No newline at end of file diff --git a/api/src/main/java/fr/traqueur/morebees/api/models/Upgrade.java b/api/src/main/java/fr/traqueur/morebees/api/models/Upgrade.java new file mode 100644 index 0000000..19fd45a --- /dev/null +++ b/api/src/main/java/fr/traqueur/morebees/api/models/Upgrade.java @@ -0,0 +1,34 @@ +package fr.traqueur.morebees.api.models; + +import fr.traqueur.morebees.api.Messages; +import fr.traqueur.morebees.api.serialization.Keys; +import fr.traqueur.morebees.api.serialization.datas.UpgradeDataType; +import fr.traqueur.morebees.api.settings.ItemStackWrapper; +import fr.traqueur.morebees.api.util.Formatter; +import fr.traqueur.structura.api.Loadable; +import org.bukkit.inventory.ItemStack; +import org.bukkit.persistence.PersistentDataContainer; +import org.jetbrains.annotations.NotNull; + +public record Upgrade(String id, ItemStackWrapper item, int maxBees, double productionMultiplier, boolean produceBlocks) implements Loadable { + + public static Upgrade NONE = new Upgrade("none", ItemStackWrapper.EMPTY, 3, 1.0, false); + + public Formatter[] formatters() { + return Formatter.all( + "max-bees", maxBees, + "production-multiplier", productionMultiplier, + "produce-blocks", produceBlocks ? Messages.PRODUCE_BLOCKS_YES.raw() : Messages.PRODUCE_BLOCKS_NO.raw() + ); + } + + public @NotNull ItemStack build() { + ItemStack itemStack = item.build(this.formatters()); + itemStack.editMeta(meta -> { + PersistentDataContainer container = meta.getPersistentDataContainer(); + Keys.UPGRADE_ID.set(container, UpgradeDataType.INSTANCE, this); + }); + return itemStack; + } + +} diff --git a/api/src/main/java/fr/traqueur/morebees/api/serialization/Keys.java b/api/src/main/java/fr/traqueur/morebees/api/serialization/Keys.java new file mode 100644 index 0000000..e310c1c --- /dev/null +++ b/api/src/main/java/fr/traqueur/morebees/api/serialization/Keys.java @@ -0,0 +1,76 @@ +package fr.traqueur.morebees.api.serialization; + +import fr.traqueur.morebees.api.BeePlugin; +import org.bukkit.NamespacedKey; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; +import org.bukkit.plugin.java.JavaPlugin; + +import java.util.Optional; + +/** + * Enum representing the keys used for persistent data storage in the More Bees plugin. + * Each key corresponds to a specific piece of data that can be stored in a {@link PersistentDataContainer}. + */ +public enum Keys { + + BEE_TYPE, + + BEEHIVE, + + INTERNAL_BEEHIVE_BEE_TYPES, + INTERNAL_BEEHIVE_UPGRADE, + INTERNAL_BEEHIVE_DISPLAY_ID, + + TOOL_ID, + MAX_BEES, + BEES, + + INTERNAL_BEE_DATA_BEE_TYPE, + INTERNAL_BEE_DATA_HAS_NECTAR, + INTERNAL_BEE_DATA_IS_ADULT, + UPGRADE_ID; + + private static final BeePlugin PLUGIN = JavaPlugin.getPlugin(BeePlugin.class); + + /** + * Retrieves the value associated with this key from the given {@link PersistentDataContainer}. + * The value is retrieved using the {@link PersistentDataType} provided. + * @param container the {@link PersistentDataContainer} from which to retrieve the value + * @param type the {@link PersistentDataType} that defines how to interpret the stored data + * @return an {@link Optional} containing the value if it exists, or empty if it does not + * @param the type of the value to retrieve + */ + public Optional get(PersistentDataContainer container, PersistentDataType type) { + NamespacedKey key = new NamespacedKey(PLUGIN, name().toLowerCase()); + return Optional.ofNullable(container.get(key, type)); + } + + /** + * Retrieves the value associated with this key from the given {@link PersistentDataContainer}. + * If the value does not exist, the provided default value is returned. + * @param container the {@link PersistentDataContainer} from which to retrieve the value + * @param type the {@link PersistentDataType} that defines how to interpret the stored data + * @param def the default value to return if the key does not exist + * @return the value associated with this key, or the default value if it does not exist + * @param the type of the value to retrieve + */ + public T get(PersistentDataContainer container, PersistentDataType type, T def) { + NamespacedKey key = new NamespacedKey(PLUGIN, name().toLowerCase()); + return container.getOrDefault(key, type, def); + } + + /** + * Sets the value associated with this key in the given {@link PersistentDataContainer}. + * The value is stored using the provided {@link PersistentDataType}. + * @param container the {@link PersistentDataContainer} in which to store the value + * @param type the {@link PersistentDataType} that defines how to store the data + * @param value the value to store + * @param the type of the value to store + */ + public void set(PersistentDataContainer container, PersistentDataType type, T value) { + NamespacedKey key = new NamespacedKey(PLUGIN, name().toLowerCase()); + container.set(key, type, value); + } + +} \ No newline at end of file diff --git a/api/src/main/java/fr/traqueur/morebees/api/serialization/MapDataType.java b/api/src/main/java/fr/traqueur/morebees/api/serialization/MapDataType.java new file mode 100644 index 0000000..2c4bbb6 --- /dev/null +++ b/api/src/main/java/fr/traqueur/morebees/api/serialization/MapDataType.java @@ -0,0 +1,153 @@ +package fr.traqueur.morebees.api.serialization; + +import org.bukkit.NamespacedKey; +import org.bukkit.persistence.PersistentDataAdapterContext; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; +import java.util.stream.IntStream; + +/** + * A {@link PersistentDataType} for {@link Map}s + * @param The key type + * @param The value type + */ +@SuppressWarnings("unused") +public class MapDataType implements PersistentDataType> { + + private static final String E_KEY_MUST_NOT_BE_NULL = "Maps stored in a PersistentDataContainer must not contain any null keys."; + private static final String E_NOT_A_MAP = "Not a map."; + private final NamespacedKey KEY_SIZE = Utils.getKeyKey("s"); + + + private final Class> mapClazz; + private final Supplier> mapSupplier; + private final PersistentDataType keyDataType; + private final PersistentDataType valueDataType; + + /** + * Creates a new {@link MapDataType} + * @param mapSupplier A {@link Supplier} for the map type + * @param keyDataType The {@link PersistentDataType} for the keys + * @param valueDataType The {@link PersistentDataType} for the values + */ + @SuppressWarnings("unchecked") + public MapDataType(@NotNull final Supplier> mapSupplier, + @NotNull final PersistentDataType keyDataType, + @NotNull final PersistentDataType valueDataType) { + this.mapSupplier = mapSupplier; + this.mapClazz = (Class>) mapSupplier.get().getClass(); + this.keyDataType = keyDataType; + this.valueDataType = valueDataType; + } + + @NotNull + @Override + public Class getPrimitiveType() { + return PersistentDataContainer.class; + } + + @NotNull + @Override + public Class> getComplexType() { + return mapClazz; + } + + @NotNull + @Override + public PersistentDataContainer toPrimitive(@NotNull final Map map, @NotNull final PersistentDataAdapterContext context) { + final PersistentDataContainer pdc = context.newPersistentDataContainer(); + int index = 0; + final int size = map.size(); + pdc.set(KEY_SIZE, PersistentDataType.INTEGER, size); + for (final K key : map.keySet()) { + if (key == null) { + throw new IllegalArgumentException(E_KEY_MUST_NOT_BE_NULL); + } + final V value = map.get(key); + if (value != null) { + pdc.set(Utils.getValueKey(index), valueDataType, value); + } + pdc.set(Utils.getKeyKey(index++), keyDataType, key); + } + return pdc; + } + + @NotNull + @Override + public Map fromPrimitive(@NotNull final PersistentDataContainer pdc, @NotNull final PersistentDataAdapterContext context) { + final Map map = mapSupplier.get(); + final Integer size = pdc.get(KEY_SIZE, PersistentDataType.INTEGER); + if (size == null) { + throw new IllegalArgumentException(E_NOT_A_MAP); + } + for (int i = 0; i < size; i++) { + final K key = pdc.get(Utils.getKeyKey(i), keyDataType); + map.put(key, pdc.get(Utils.getValueKey(i), valueDataType)); + } + return map; + } + + static class Utils { + + private Utils() { + + } + + private static final Map KEY_KEYS = new HashMap<>(); + private static final Map VALUE_KEYS = new HashMap<>(); + + static { + // Caching the first 100 keys. I think that's reasonable for most use cases + IntStream.range(0, 100).forEach(number -> { + getValueKey(number); + getKeyKey(number); + }); + } + + /** + * Returns a NamespacedKey for the given key index. + * + * @param index The index of the key + * @return The NamespacedKey + */ + public static NamespacedKey getKeyKey(final int index) { + return getKeyKey(String.valueOf(index)); + } + + /** + * Returns a NamespacedKey for the given key name. + * + * @param name The name of the key + * @return The NamespacedKey + */ + public static NamespacedKey getKeyKey(final String name) { + return KEY_KEYS.computeIfAbsent(name, __ -> NamespacedKey.fromString("k:" + name)); + } + + /** + * Returns a NamespacedKey for the given value index. + * + * @param index The index of the value + * @return The NamespacedKey + */ + public static NamespacedKey getValueKey(final int index) { + return getValueKey(String.valueOf(index)); + } + + /** + * Returns a NamespacedKey for the given value name. + * + * @param name The name of the value + * @return The NamespacedKey + */ + public static NamespacedKey getValueKey(final String name) { + return VALUE_KEYS.computeIfAbsent(name, __ -> NamespacedKey.fromString("v:" + name)); + } + } + +} \ No newline at end of file diff --git a/api/src/main/java/fr/traqueur/morebees/api/serialization/datas/BeeDataDataType.java b/api/src/main/java/fr/traqueur/morebees/api/serialization/datas/BeeDataDataType.java new file mode 100644 index 0000000..8b4f707 --- /dev/null +++ b/api/src/main/java/fr/traqueur/morebees/api/serialization/datas/BeeDataDataType.java @@ -0,0 +1,39 @@ +package fr.traqueur.morebees.api.serialization.datas; + +import fr.traqueur.morebees.api.models.BeeData; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a data type for serializing and deserializing {@link BeeData} objects using Bukkit's + * persistent data API. + *

+ * This class provides the necessary methods to convert between {@link PersistentDataContainer} + * and {@link BeeData}. + */ +public abstract class BeeDataDataType implements PersistentDataType { + + /** + * Singleton instance of the {@link BeeDataDataType}. + */ + public static BeeDataDataType INSTANCE; + + /** + * Returns the class type of the complex data type, which is {@link BeeData}. + * @return the class type of the complex data type + */ + @Override + public @NotNull Class getComplexType() { + return BeeData.class; + } + + /** + * Returns the class type of the primitive data type, which is {@link PersistentDataContainer}. + * @return the class type of the primitive data type + */ + @Override + public @NotNull Class getPrimitiveType() { + return PersistentDataContainer.class; + } +} diff --git a/api/src/main/java/fr/traqueur/morebees/api/serialization/datas/BeeTypeDataType.java b/api/src/main/java/fr/traqueur/morebees/api/serialization/datas/BeeTypeDataType.java new file mode 100644 index 0000000..1c2a68f --- /dev/null +++ b/api/src/main/java/fr/traqueur/morebees/api/serialization/datas/BeeTypeDataType.java @@ -0,0 +1,37 @@ +package fr.traqueur.morebees.api.serialization.datas; + +import fr.traqueur.morebees.api.models.BeeType; +import org.bukkit.persistence.PersistentDataType; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a data type for serializing and deserializing {@link BeeType} objects using Bukkit's + * persistent data API. + *

+ * This class provides the necessary methods to convert between {@link String} and {@link BeeType}. + */ +public abstract class BeeTypeDataType implements PersistentDataType { + + /** + * Singleton instance of the {@link BeeTypeDataType}. + */ + public static BeeTypeDataType INSTANCE; + + /** + * Returns the class type of the complex data type, which is {@link BeeType}. + * @return the class type of the complex data type + */ + @Override + public @NotNull Class getComplexType() { + return BeeType.class; + } + + /** + * Returns the class type of the primitive data type, which is {@link String}. + * @return the class type of the primitive data type + */ + @Override + public @NotNull Class getPrimitiveType() { + return String.class; + } +} diff --git a/api/src/main/java/fr/traqueur/morebees/api/serialization/datas/BeehiveDataType.java b/api/src/main/java/fr/traqueur/morebees/api/serialization/datas/BeehiveDataType.java new file mode 100644 index 0000000..47249c6 --- /dev/null +++ b/api/src/main/java/fr/traqueur/morebees/api/serialization/datas/BeehiveDataType.java @@ -0,0 +1,39 @@ +package fr.traqueur.morebees.api.serialization.datas; + +import fr.traqueur.morebees.api.models.Beehive; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a data type for serializing and deserializing {@link Beehive} objects using Bukkit's + * persistent data API. + *

+ * This class provides the necessary methods to convert between {@link PersistentDataContainer} + * and {@link Beehive}. + */ +public abstract class BeehiveDataType implements PersistentDataType { + + /** + * Singleton instance of the {@link BeehiveDataType}. + */ + public static BeehiveDataType INSTANCE; + + /** + * Returns the class type of the complex data type, which is {@link Beehive}. + * @return the class type of the complex data type + */ + @Override + public @NotNull Class getComplexType() { + return Beehive.class; + } + + /** + * Returns the class type of the primitive data type, which is {@link PersistentDataContainer}. + * @return the class type of the primitive data type + */ + @Override + public @NotNull Class getPrimitiveType() { + return PersistentDataContainer.class; + } +} diff --git a/api/src/main/java/fr/traqueur/morebees/api/serialization/datas/ToolDataType.java b/api/src/main/java/fr/traqueur/morebees/api/serialization/datas/ToolDataType.java new file mode 100644 index 0000000..134bbe0 --- /dev/null +++ b/api/src/main/java/fr/traqueur/morebees/api/serialization/datas/ToolDataType.java @@ -0,0 +1,37 @@ +package fr.traqueur.morebees.api.serialization.datas; + +import fr.traqueur.morebees.api.models.Tool; +import org.bukkit.persistence.PersistentDataType; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a data type for serializing and deserializing {@link Tool} objects using Bukkit's + * persistent data API. + *

+ * This class provides the necessary methods to convert between {@link String} and {@link Tool}. + */ +public abstract class ToolDataType implements PersistentDataType { + + /** + * Singleton instance of the {@link ToolDataType}. + */ + public static ToolDataType INSTANCE; + + /** + * Returns the class type of the complex data type, which is {@link Tool}. + * @return the class type of the complex data type + */ + @Override + public @NotNull Class getComplexType() { + return Tool.class; + } + + /** + * Returns the class type of the primitive data type, which is {@link String}. + * @return the class type of the primitive data type + */ + @Override + public @NotNull Class getPrimitiveType() { + return String.class; + } +} diff --git a/api/src/main/java/fr/traqueur/morebees/api/serialization/datas/UpgradeDataType.java b/api/src/main/java/fr/traqueur/morebees/api/serialization/datas/UpgradeDataType.java new file mode 100644 index 0000000..87c0501 --- /dev/null +++ b/api/src/main/java/fr/traqueur/morebees/api/serialization/datas/UpgradeDataType.java @@ -0,0 +1,37 @@ +package fr.traqueur.morebees.api.serialization.datas; + +import fr.traqueur.morebees.api.models.Upgrade; +import org.bukkit.persistence.PersistentDataType; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a data type for serializing and deserializing {@link Upgrade} objects using Bukkit's + * persistent data API. + *

+ * This class provides the necessary methods to convert between {@link String} and {@link Upgrade}. + */ +public abstract class UpgradeDataType implements PersistentDataType { + + /** + * Singleton instance of the {@link UpgradeDataType}. + */ + public static UpgradeDataType INSTANCE; + + /** + * Returns the class type of the complex data type, which is {@link Upgrade}. + * @return the class type of the complex data type + */ + @Override + public @NotNull Class getComplexType() { + return Upgrade.class; + } + + /** + * Returns the class type of the primitive data type, which is {@link String}. + * @return the class type of the primitive data type + */ + @Override + public @NotNull Class getPrimitiveType() { + return String.class; + } +} diff --git a/api/src/main/java/fr/traqueur/morebees/api/settings/BreedSettings.java b/api/src/main/java/fr/traqueur/morebees/api/settings/BreedSettings.java new file mode 100644 index 0000000..30c418a --- /dev/null +++ b/api/src/main/java/fr/traqueur/morebees/api/settings/BreedSettings.java @@ -0,0 +1,29 @@ +package fr.traqueur.morebees.api.settings; + +import fr.traqueur.morebees.api.models.BeeType; +import fr.traqueur.morebees.api.models.Breed; +import fr.traqueur.morebees.api.models.Mutation; +import org.bukkit.block.Block; + +import java.util.List; +import java.util.Optional; + +/** + * Represents the settings for breeding bees, including the breeds and mutations available. + * This class provides methods to retrieve mutations based on parent bee types and blocks. + */ +public record BreedSettings(List breeds, List mutations) implements Settings { + + /** + * Retrieves the mutation for a given parent bee type and block. + * + * @param parent the parent bee type + * @param block the block to check for mutation compatibility + * @return an Optional containing the Mutation if found, otherwise empty + */ + public Optional getMutation(BeeType parent, Block block) { + return this.mutations.stream() + .filter(mutation -> mutation.parent().equals(parent.type()) && mutation.canMutate(block)) + .findFirst(); + } +} diff --git a/api/src/main/java/fr/traqueur/morebees/api/settings/GlobalSettings.java b/api/src/main/java/fr/traqueur/morebees/api/settings/GlobalSettings.java new file mode 100644 index 0000000..b799daa --- /dev/null +++ b/api/src/main/java/fr/traqueur/morebees/api/settings/GlobalSettings.java @@ -0,0 +1,52 @@ +package fr.traqueur.morebees.api.settings; + +import fr.traqueur.morebees.api.models.BeeType; +import fr.traqueur.structura.annotations.Options; + +import java.util.List; +import java.util.Optional; + +/** + * Global settings for the MoreBees plugin. + * This class holds the configuration for bees, bee tools, and other global settings. + */ +public record GlobalSettings(boolean debug, + String flyAnimation, + List bees, + ItemStackWrapper beeBox, + int beeBoxSize, + ItemStackWrapper beeJar, + @Options(optional = true) List beehiveLore + ) implements Settings { + + /** + * Retrieves a bee type by its identifier. + * This method searches through the list of bee types and returns the one that matches the provided + * type identifier. + * If no matching bee type is found, it returns an empty Optional. + * @param type The identifier of the bee type to retrieve. + * @return An Optional containing the BeeType if found, otherwise empty. + */ + public Optional getBeeType(String type) { + return bees.stream() + .filter(b -> b.type().equals(type)) + .findFirst(); + } + + /** + * Checks if the global settings contain all specified bee types. + * This method verifies if the provided bee types are present in the global settings. + * It returns true if all specified types are found, otherwise false. + * @param type The bee types to check for. + * @return True if all specified bee types are present, false otherwise. + */ + public boolean contains(String... type) { + for (String t : type) { + if (bees.stream().noneMatch(b -> b.type().equals(t))) { + return false; + } + } + return true; + } + +} diff --git a/api/src/main/java/fr/traqueur/morebees/api/settings/ItemStackWrapper.java b/api/src/main/java/fr/traqueur/morebees/api/settings/ItemStackWrapper.java new file mode 100644 index 0000000..14c1bca --- /dev/null +++ b/api/src/main/java/fr/traqueur/morebees/api/settings/ItemStackWrapper.java @@ -0,0 +1,48 @@ +package fr.traqueur.morebees.api.settings; + +import fr.traqueur.morebees.api.util.Formatter; +import fr.traqueur.morebees.api.util.MiniMessageHelper; +import fr.traqueur.morebees.api.util.Util; +import fr.traqueur.structura.api.Loadable; +import org.bukkit.inventory.ItemStack; + +import javax.annotation.Nullable; +import java.util.List; + +/** + * Represents a wrapper for an ItemStack with material, name, and lore. + * This class is used to create ItemStacks with formatted names and lore. + * It provides a method to build the ItemStack with the specified properties. + */ +public record ItemStackWrapper(String material, @Nullable String name, @Nullable List lore) implements Loadable { + + /** + * Get the Wrapper for the AIR item. + * This is a static instance representing an empty ItemStack. + * It can be used when no specific item is needed. + */ + public static final ItemStackWrapper EMPTY = new ItemStackWrapper("AIR", null, null); + + /** + * Constructs an ItemStack with formatters applied to the name and lore. + * + * @param formatters The formatters to apply to the name and lore. + * These formatters can be used to replace placeholders in the name and lore. + * @return An ItemStack with the specified material, name, and lore. + */ + public ItemStack build(Formatter... formatters) { + ItemStack base = Util.getItemFromId(material); + base.editMeta(meta -> { + if (name != null) + meta.itemName(MiniMessageHelper.parse(Formatter.format(name, formatters))); + + if (lore != null && !lore.isEmpty()) { + meta.lore(Util.parseLore(lore, formatters)); + } + }); + return base; + } + + + +} diff --git a/api/src/main/java/fr/traqueur/morebees/api/settings/Settings.java b/api/src/main/java/fr/traqueur/morebees/api/settings/Settings.java new file mode 100644 index 0000000..4b203dd --- /dev/null +++ b/api/src/main/java/fr/traqueur/morebees/api/settings/Settings.java @@ -0,0 +1,10 @@ +package fr.traqueur.morebees.api.settings; + +import fr.traqueur.structura.api.Loadable; + +/** + * Marker interface for settings in the More Bees API. + * This interface can be used to categorize or tag classes that represent settings. + */ +public interface Settings extends Loadable { +} diff --git a/api/src/main/java/fr/traqueur/morebees/api/settings/UpgradeSettings.java b/api/src/main/java/fr/traqueur/morebees/api/settings/UpgradeSettings.java new file mode 100644 index 0000000..5bf3fe4 --- /dev/null +++ b/api/src/main/java/fr/traqueur/morebees/api/settings/UpgradeSettings.java @@ -0,0 +1,23 @@ +package fr.traqueur.morebees.api.settings; + +import fr.traqueur.morebees.api.models.Upgrade; + +import java.util.List; +import java.util.Optional; + +/** + * Represents the settings for upgrades in the More Bees API. + * This class contains a list of available upgrades and provides methods to access them. + */ +public record UpgradeSettings(List upgrades) implements Settings { + + /** + * Retrieves an upgrade by its ID. + * + * @param id the ID of the upgrade to retrieve + * @return an Optional containing the Upgrade if found, or empty if not found + */ + public Optional getUpgrade(String id) { + return upgrades.stream().filter(upgrade -> upgrade.id().equals(id)).findFirst(); + } +} diff --git a/api/src/main/java/fr/traqueur/morebees/api/util/Formatter.java b/api/src/main/java/fr/traqueur/morebees/api/util/Formatter.java new file mode 100644 index 0000000..1fcf490 --- /dev/null +++ b/api/src/main/java/fr/traqueur/morebees/api/util/Formatter.java @@ -0,0 +1,115 @@ +package fr.traqueur.morebees.api.util; + +import fr.traqueur.morebees.api.BeePlugin; + +import java.util.function.Function; + +/** + * A utility class for formatting strings with dynamic content. + * It allows you to define patterns and their corresponding suppliers + * that will replace the patterns in a given text. + */ +public class Formatter { + + /** + * The pattern to be replaced in the text. + */ + private final String pattern; + /** + * A function that provides the content to replace the pattern. + * It takes a BeePlugin instance as an argument to access plugin-specific data. + */ + private final Function supplier; + + /** + * Formats the given text by replacing all occurrences of the defined patterns + * with their corresponding content provided by the suppliers. + * + * @param text The text to format. + * @param formatters An array of Formatter instances defining the patterns and their suppliers. + * @return The formatted text with all patterns replaced. + */ + public static String format(String text, Formatter... formatters) { + if (text == null) return null; + for (Formatter formatter : formatters) { + text = formatter.handle(BeePlugin.getPlugin(BeePlugin.class), text); + } + return text; + } + + /** + * Creates an array of Formatter instances from the provided pattern and supplier pairs. + * Each pair consists of a String pattern and an Object supplier. + * + * @param objects An even number of arguments where each pair is a pattern followed by a supplier. + * @return An array of Formatter instances. + * @throws IllegalArgumentException if the number of arguments is odd or if the patterns are not Strings. + */ + public static Formatter[] all(Object... objects) { + if(objects.length % 2 != 0) { + throw new IllegalArgumentException("You must provide an even number of arguments (pattern, supplier) pairs."); + } + Formatter[] formatters = new Formatter[objects.length / 2]; + for (int i = 0; i < objects.length; i += 2) { + if (!(objects[i] instanceof String)) { + throw new IllegalArgumentException("Each pattern must be a String and each supplier must be a Object."); + } + formatters[i / 2] = new Formatter((String) objects[i], objects[i + 1]); + } + return formatters; + } + + /** + * Creates a new Formatter instance with the specified pattern and supplier. + * @param pattern the pattern to be replaced in the text + * @param supplier the supplier that provides the content to replace the pattern + */ + private Formatter(String pattern, Object supplier) { + this.pattern = pattern; + this.supplier = (api) -> supplier.toString(); + } + +/** + * Creates a new Formatter instance with the specified pattern and supplier function. + * @param pattern the pattern to be replaced in the text + * @param supplier the function that provides the content to replace the pattern + */ + private Formatter(String pattern, Function supplier) { + this.pattern = pattern; + this.supplier = supplier; + } + + /** + * Factory method to create a Formatter instance with a String supplier. + * + * @param pattern the pattern to be replaced in the text + * @param supplier the supplier that provides the content to replace the pattern + * @return a new Formatter instance + */ + public static Formatter format(String pattern, Object supplier) { + return new Formatter(pattern, supplier); + } + + /** + * Factory method to create a Formatter instance with a Function supplier. + * + * @param pattern the pattern to be replaced in the text + * @param supplier the function that provides the content to replace the pattern + * @return a new Formatter instance + */ + public static Formatter format(String pattern, Function supplier) { + return new Formatter(pattern, supplier); + } + + /** + * Handles the replacement of the pattern in the given message with the content provided by the supplier. + * + * @param api the BeePlugin instance to access plugin-specific data + * @param message the message containing the pattern to be replaced + * @return the message with the pattern replaced by the content from the supplier + */ + public String handle(BeePlugin api, String message) { + String content = this.supplier.apply(api); + return message.replaceAll("%" + this.pattern + "%", content); + } +} \ No newline at end of file diff --git a/api/src/main/java/fr/traqueur/morebees/api/util/MiniMessageHelper.java b/api/src/main/java/fr/traqueur/morebees/api/util/MiniMessageHelper.java new file mode 100644 index 0000000..69750d7 --- /dev/null +++ b/api/src/main/java/fr/traqueur/morebees/api/util/MiniMessageHelper.java @@ -0,0 +1,38 @@ +package fr.traqueur.morebees.api.util; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import org.jetbrains.annotations.NotNull; + +/** + * A utility class for parsing and unparsing MiniMessage strings. + * This class provides methods to convert MiniMessage formatted strings + * into Adventure Components and vice versa. + */ +public class MiniMessageHelper { + + /** + * The MiniMessage instance used for parsing and unparsing. + */ + private final static MiniMessage MINI_MESSAGE = MiniMessage.miniMessage(); + + /** + * Parses a MiniMessage formatted string into an Adventure Component. + * + * @param message the MiniMessage formatted string to parse + * @return the parsed Adventure Component + */ + public static Component parse(String message) { + return MINI_MESSAGE.deserialize(message); + } + + /** + * Unparses an Adventure Component into a MiniMessage formatted string. + * + * @param component the Adventure Component to unparse + * @return the MiniMessage formatted string + */ + public static String unparse(@NotNull Component component) { + return MINI_MESSAGE.serialize(component); + } +} diff --git a/api/src/main/java/fr/traqueur/morebees/api/util/Util.java b/api/src/main/java/fr/traqueur/morebees/api/util/Util.java new file mode 100644 index 0000000..45f933e --- /dev/null +++ b/api/src/main/java/fr/traqueur/morebees/api/util/Util.java @@ -0,0 +1,101 @@ +package fr.traqueur.morebees.api.util; + +import fr.traqueur.morebees.api.hooks.Hook; +import fr.traqueur.morebees.api.hooks.ItemProviderHook; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.TextDecoration; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.Item; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.*; +import java.util.function.BiConsumer; + +/** + * Utility class providing various helper methods for the MoreBees API. + * This class includes methods for handling optional values, validating blocks, + * retrieving items by ID, parsing lore, and giving items to players. + */ +public class Util { + + /** + * Checks if both optional values are present and applies the given consumer to them. + * + * @param a the first optional value + * @param b the second optional value + * @param consumer the consumer to apply if both values are present + * @param the type of the first optional value + * @param the type of the second optional value + */ + public static void ifBothPresent(Optional a, Optional b, BiConsumer consumer) { + if (a.isPresent() && b.isPresent()) { + consumer.accept(a.get(), b.get()); + } + } + + /** + * Checks if the given block is valid based on the provided list of valid types. + * + * @param block the block to check + * @param validTypes a list of valid block types + * @return true if the block is valid, false otherwise + */ + public static boolean isValidBlock(Block block, List validTypes) { + Material type = block.getType(); + Set hooks = Hook.getByClass(ItemProviderHook.class); + String itemName = hooks.stream().map(hook -> hook.getBlockName(block)).filter(Objects::nonNull).findFirst().orElse(type.name()); + return validTypes.contains(itemName); + } + + /** + * Retrieves an ItemStack from the given ID, using registered ItemProviderHooks. + * If no hook provides the item, it defaults to creating an ItemStack from the Material. + * + * @param id the ID of the item + * @return the ItemStack corresponding to the ID + */ + public static ItemStack getItemFromId(String id) { + Set hooks = Hook.getByClass(ItemProviderHook.class); + return hooks.stream() + .map(hook -> hook.getItemFromId(id)) + .filter(Objects::nonNull) + .findFirst() + .orElse(ItemStack.of(Material.valueOf(id))); + } + + /** + * Parses a list of lore strings into a list of Adventure Components. + * Each line is formatted using the provided formatters, split by new lines, + * and parsed into components with italic text decoration disabled. + * + * @param lore the list of lore strings to parse + * @param formatters optional formatters to apply to each line + * @return a list of parsed Adventure Components + */ + public static List parseLore(List lore, Formatter... formatters) { + return lore.stream() + .map(line -> Formatter.format(line, formatters)) + .filter(Objects::nonNull) + .flatMap(line -> Arrays.stream(line.split("\n"))) + .filter(s -> !s.isEmpty()) + .map(MiniMessageHelper::parse) + .map(component -> component.decoration(TextDecoration.ITALIC, false)) + .toList(); + } + + /** + * Gives an item to a player, dropping it at the player's location if the inventory is full. + * + * @param player the player to give the item to + * @param toGive the ItemStack to give + */ + public static void giveItem(Player player, @NotNull ItemStack toGive) { + player.getInventory().addItem(toGive).forEach((slot, item) -> { + Item itemDropped = player.getWorld().dropItem(player.getLocation(), item); + itemDropped.setOwner(player.getUniqueId()); + }); + } +} diff --git a/build.gradle b/build.gradle index 69d20cd..b205bab 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ project.ext { libraries = [] } -allprojects { +allprojects { subproject -> version = rootProject.property('version') apply from: rootProject.file('gradle/add-library-dependency.gradle') @@ -30,14 +30,14 @@ allprojects { url = "https://oss.sonatype.org/content/groups/public/" } maven { url = "https://jitpack.io" } + maven { url = "https://mvn.lumine.io/repository/maven-public/" } } dependencies { - compileOnly("io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT") - - addLibraryDependency(project, "de.exlll:configlib-yaml:4.6.1") implementation("com.github.Traqueur-dev.CommandsAPI:platform-spigot:4.2.1") implementation("com.github.Traqueur-dev.CommandsAPI:core:4.2.1") + implementation("com.github.Traqueur-dev:RecipesAPI:2.0.3") + implementation("com.github.Traqueur-dev:Structura:1.0.2") } def targetJavaVersion = 21 @@ -63,21 +63,39 @@ allprojects { } shadowJar { - archiveBaseName.set(project.name) + archiveBaseName.set(rootProject.name) archiveVersion.set(rootProject.version) archiveClassifier.set('') - relocate "fr.traqueur.commands", "fr.traqueur.morebees.api.commands" + relocate "fr.traqueur.recipes", "fr.traqueur.morebees.api.recipes" + relocate "fr.traqueur.structura", "fr.traqueur.morebees.api.structura" } } +repositories { + maven { url = "https://maven.devs.beer/" } + maven {url = "https://repo.oraxen.com/releases" } + maven {url = "https://repo.nexomc.com/releases" } +} + dependencies { + compileOnly("io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT") implementation(project(":api")) + + compileOnly("com.ticxo.modelengine:ModelEngine:R4.0.8") + compileOnly("dev.lone:api-itemsadder:4.0.10") + compileOnly("io.th0rgal:oraxen:1.191.0") + compileOnly("com.nexomc:nexo:1.9.0") +} + +shadowJar { + mergeServiceFiles() + destinationDirectory.set(rootProject.targetFolder) } tasks { runServer { - minecraftVersion("1.21") + minecraftVersion("1.21.4") } } @@ -99,6 +117,17 @@ tasks.withType(JavaCompile).configureEach { } } +tasks.register("cleanTarget") { + doLast { + delete rootProject.targetFolder + delete rootProject.apiFolder + } +} + +tasks.named("clean") { + dependsOn "cleanTarget" +} + processResources { filteringCharset = 'UTF-8' diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..bf24bea --- /dev/null +++ b/docs/index.html @@ -0,0 +1,525 @@ + + + + + + Spiral Search Algorithm - MoreBees Plugin + + + +

+

🐝 Spiral Search Algorithm

+

Optimized block searching for Minecraft bee AI - MoreBees Plugin

+ +
+ +
+
+

❌ Original Method (Full Cube)

+
+
+

Performance Stats:

+

Blocks scanned: 0

+

Time complexity: O(r³)

+

Search pattern: Sequential

+

Early termination: No

+
+
+ +
+

✅ Optimized Method (Spiral)

+
+
+

Performance Stats:

+

Blocks scanned: 0

+

Time complexity: O(r²) average

+

Search pattern: Radius-based spiral

+

Early termination: Yes

+
+
+
+ +
+ + + +
+ +
+

🧠 How the Spiral Algorithm Works

+
+ Step 1: Start searching at radius 1 (8 blocks around the bee) +
+
+ Step 2: If no target found, expand to radius 2 (next 16 blocks) +
+
+ Step 3: Continue expanding radius by radius until a target is found +
+
+ Key Advantage: Stop immediately when first target is found (guaranteed to be closest!) +
+
+ +
+ // Java Implementation Example + private Block findNearestTargetBlock() { + Location beeLoc = bee.getLocation(); + int searchRadius = beeType.getSearchRadius(); + + // Spiral search - start from closest blocks + for (int radius = 1; radius <= searchRadius; radius++) { + Block found = searchAtRadius(beeLoc, radius); + if (found != null) { + return found; // Return immediately - closest found! + } + } + return null; + } +
+ +
+

📊 Performance Comparison

+
+
+

Original Cube Method

+

Radius 5: 1,331 blocks

+

Radius 10: 9,261 blocks

+

Server impact: High TPS lag

+

Scalability: Poor with multiple bees

+
+
+

Spiral Method

+

Radius 5: ~100 blocks average

+

Radius 10: ~400 blocks average

+

Server impact: Minimal TPS impact

+

Scalability: Excellent with 100+ bees

+
+
+
+ + +
+ + + + \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 2ba6280..beb72cc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=1.0.0-SNAPSHOT \ No newline at end of file +version=1.0.0 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a441313..ca025c8 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/src/main/java/fr/traqueur/morebees/MoreBees.java b/src/main/java/fr/traqueur/morebees/MoreBees.java index 622ba6c..6b5e2da 100644 --- a/src/main/java/fr/traqueur/morebees/MoreBees.java +++ b/src/main/java/fr/traqueur/morebees/MoreBees.java @@ -1,16 +1,154 @@ package fr.traqueur.morebees; -import org.bukkit.plugin.java.JavaPlugin; +import fr.traqueur.commands.spigot.CommandManager; +import fr.traqueur.morebees.api.BeePlugin; +import fr.traqueur.morebees.api.Logger; +import fr.traqueur.morebees.api.Messages; +import fr.traqueur.morebees.api.managers.BeeManager; +import fr.traqueur.morebees.api.managers.BeehiveManager; +import fr.traqueur.morebees.api.managers.ToolsManager; +import fr.traqueur.morebees.api.managers.UpgradesManager; +import fr.traqueur.morebees.api.models.BeeType; +import fr.traqueur.morebees.api.models.Tool; +import fr.traqueur.morebees.api.models.Upgrade; +import fr.traqueur.morebees.api.settings.BreedSettings; +import fr.traqueur.morebees.api.settings.GlobalSettings; +import fr.traqueur.morebees.api.settings.Settings; +import fr.traqueur.morebees.api.settings.UpgradeSettings; +import fr.traqueur.morebees.commands.MoreBeesRootCommand; +import fr.traqueur.morebees.commands.arguments.BeeTypeArgument; +import fr.traqueur.morebees.commands.arguments.ToolsArgument; +import fr.traqueur.morebees.commands.arguments.UpgradeArgument; +import fr.traqueur.morebees.hooks.Hooks; +import fr.traqueur.morebees.managers.BeeManagerImpl; +import fr.traqueur.morebees.managers.BeehiveManagerImpl; +import fr.traqueur.morebees.managers.ToolsManagerImpl; +import fr.traqueur.morebees.managers.UpgradesManagerImpl; +import fr.traqueur.morebees.recipes.MoreBeesHook; +import fr.traqueur.morebees.serialization.*; +import fr.traqueur.recipes.api.RecipesAPI; +import fr.traqueur.recipes.api.hook.Hook; +import fr.traqueur.structura.api.Structura; +import fr.traqueur.structura.exceptions.StructuraException; +import org.bukkit.Bukkit; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.Map; public final class MoreBees extends BeePlugin { - @Override + private final Map, Settings> settings = new HashMap<>(); + + private RecipesAPI recipesAPI; + private CommandManager<@NotNull BeePlugin> commandManager; + public void onEnable() { + long startTime = System.currentTimeMillis(); + + this.saveDefault("config.yml"); + GlobalSettings settings = this.reloadConfig("config.yml", GlobalSettings.class); + + Logger.init(this.getSLF4JLogger(), settings.debug()); + + this.saveDefaultConfig(); + this.reloadConfig(); + + Hooks.initAll(this); + + Hook.addHook(new MoreBeesHook(this)); + Bukkit.getScheduler().runTask(this, () -> { + this.recipesAPI = new RecipesAPI(this, this.getSettings(GlobalSettings.class).debug(), true); + }); + + UpgradeDataTypeImpl.init(this); + BeeTypeDataTypeImpl.init(this); + BeehiveDataTypeImpl.init(); + ToolDataTypeImpl.init(); + BeeDataDataTypeImpl.init(); + + this.registerManager(BeeManager.class, new BeeManagerImpl()); + this.registerManager(UpgradesManager.class, new UpgradesManagerImpl()); + this.registerManager(BeehiveManager.class, new BeehiveManagerImpl()); + this.registerManager(ToolsManager.class, new ToolsManagerImpl()); + + this.commandManager = new CommandManager<>(this); + commandManager.setDebug(settings.debug()); + commandManager.setLogger(new fr.traqueur.commands.api.logging.Logger() { + @Override + public void error(String message) { + Logger.severe(message); + } + + @Override + public void info(String message) { + Logger.info(message); + } + }); + + commandManager.registerConverter(BeeType.class, new BeeTypeArgument(this)); + commandManager.registerConverter(Tool.class, new ToolsArgument()); + commandManager.registerConverter(Upgrade.class, new UpgradeArgument(this)); + + commandManager.registerCommand(new MoreBeesRootCommand(this)); + + Logger.success("MoreBees has been enabled in {}ms", (System.currentTimeMillis() - startTime)); } - @Override public void onDisable() { + long startTime = System.currentTimeMillis(); + + Logger.success("MoreBees has been disabled in {}ms", (System.currentTimeMillis() - startTime)); + } + + @Override + public CommandManager<@NotNull BeePlugin> getCommandManager() { + return commandManager; + } + + @Override + public T getSettings(Class clazz) { + if (settings.containsKey(clazz)) { + return clazz.cast(settings.get(clazz)); + } + throw new IllegalArgumentException("Class " + clazz.getName() + " does not exist"); + } + + @Override + public void saveDefaultConfig() { + this.saveDefault("messages.yml"); + this.saveDefault("breeds.yml"); + this.saveDefault("upgrades.yml"); + } + + @Override + public void reloadConfig() { + super.reloadConfig(); + this.reloadConfig("config.yml", GlobalSettings.class); + this.reloadConfig("breeds.yml", BreedSettings.class); + this.reloadConfig("upgrades.yml", UpgradeSettings.class); + try { + Structura.loadEnum(this.getDataPath().resolve("messages.yml"), Messages.class); + } catch (StructuraException e) { + Logger.severe("Failed to load messages.yml, some messages will be by default", e); + } + } + + @Override + public RecipesAPI getRecipesAPI() { + return recipesAPI; + } + + private void saveDefault(String path) { + if (!this.getDataPath().resolve(path).toFile().exists()) { + this.saveResource(path, false); + } + } + private T reloadConfig(String path, Class clazz) { + T instance = Structura.load(this.getDataPath().resolve(path), clazz); + this.settings.put(clazz, instance); + return instance; } } diff --git a/src/main/java/fr/traqueur/morebees/commands/EggCommand.java b/src/main/java/fr/traqueur/morebees/commands/EggCommand.java new file mode 100644 index 0000000..46ff829 --- /dev/null +++ b/src/main/java/fr/traqueur/morebees/commands/EggCommand.java @@ -0,0 +1,56 @@ +package fr.traqueur.morebees.commands; + +import fr.traqueur.commands.api.arguments.Arguments; +import fr.traqueur.commands.spigot.Command; +import fr.traqueur.morebees.api.BeePlugin; +import fr.traqueur.morebees.api.Messages; +import fr.traqueur.morebees.api.models.BeeType; +import fr.traqueur.morebees.api.util.Formatter; +import fr.traqueur.morebees.api.util.Util; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Optional; + +public class EggCommand extends Command<@NotNull BeePlugin> { + /** + * The constructor of the command. + * + * @param plugin The plugin that owns the command. + */ + public EggCommand(BeePlugin plugin) { + super(plugin, "egg"); + + this.setPermission("morebees.command.egg"); + this.setDescription(Messages.EGG_COMMAND_DESC.raw()); + + this.addArgs("player", Player.class, "beetype", BeeType.class); + this.addOptionalArgs("amount", Integer.class, (sender, args) -> List.of("1", "8", "16", "32", "64")); + } + + @Override + public void execute(CommandSender sender, Arguments arguments) { + Player targetPlayer = arguments.get("player"); + BeeType beeType = arguments.get("beetype"); + Optional amountOpt = arguments.getOptional("amount"); + int amount = amountOpt.orElse(1); + + if(amount < 1 || amount > 64) { + Messages.COMMAND_AMOUNT_INVALID.send(sender, Formatter.all("amount", amount, "max-amount", 64)); + return; + } + + ItemStack egg = beeType.egg(); + if(amount > egg.getMaxStackSize()) { + Messages.COMMAND_AMOUNT_INVALID.send(sender, Formatter.all("amount", amount, "max-amount", egg.getMaxStackSize())); + return; + } + + egg.setAmount(amount); + Util.giveItem(targetPlayer, egg); + Messages.EGG_COMMAND_SUCCESS.send(sender, Formatter.all("player", targetPlayer.getName(), "amount", amount, "beetype", beeType.displayName())); + } +} diff --git a/src/main/java/fr/traqueur/morebees/commands/HoneyCommand.java b/src/main/java/fr/traqueur/morebees/commands/HoneyCommand.java new file mode 100644 index 0000000..1eab911 --- /dev/null +++ b/src/main/java/fr/traqueur/morebees/commands/HoneyCommand.java @@ -0,0 +1,57 @@ +package fr.traqueur.morebees.commands; + +import fr.traqueur.commands.api.arguments.Arguments; +import fr.traqueur.commands.spigot.Command; +import fr.traqueur.morebees.api.BeePlugin; +import fr.traqueur.morebees.api.Messages; +import fr.traqueur.morebees.api.models.BeeType; +import fr.traqueur.morebees.api.util.Formatter; +import fr.traqueur.morebees.api.util.Util; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Optional; + +public class HoneyCommand extends Command<@NotNull BeePlugin> { + /** + * The constructor of the command. + * + * @param plugin The plugin that owns the command. + */ + public HoneyCommand(BeePlugin plugin) { + super(plugin, "honey"); + + this.setPermission("morebees.command.honey"); + this.setDescription(Messages.HONEY_COMMAND_DESC.raw()); + + this.addArgs("player", Player.class, "beetype", BeeType.class, "block", Boolean.class); + this.addOptionalArgs("amount", Integer.class, (sender, args) -> List.of("1", "8", "16", "32", "64")); + } + + @Override + public void execute(CommandSender sender, Arguments arguments) { + Player targetPlayer = arguments.get("player"); + BeeType beeType = arguments.get("beetype"); + boolean block = arguments.get("block"); + Optional amountOpt = arguments.getOptional("amount"); + int amount = amountOpt.orElse(1); + + if(amount < 1 || amount > 64) { + Messages.COMMAND_AMOUNT_INVALID.send(sender, Formatter.all("amount", amount, "max-amount", 64)); + return; + } + + ItemStack honey = beeType.honey(1, block); + if(amount > honey.getMaxStackSize()) { + Messages.COMMAND_AMOUNT_INVALID.send(sender, Formatter.all("amount", amount, "max-amount", honey.getMaxStackSize())); + return; + } + + honey.setAmount(amount); + Util.giveItem(targetPlayer, honey); + Messages.HONEY_COMMAND_SUCCESS.send(sender, Formatter.all("player", targetPlayer.getName(), "amount", amount, "beetype", beeType.displayName())); + } +} diff --git a/src/main/java/fr/traqueur/morebees/commands/MoreBeesRootCommand.java b/src/main/java/fr/traqueur/morebees/commands/MoreBeesRootCommand.java new file mode 100644 index 0000000..2feeb25 --- /dev/null +++ b/src/main/java/fr/traqueur/morebees/commands/MoreBeesRootCommand.java @@ -0,0 +1,49 @@ +package fr.traqueur.morebees.commands; + +import fr.traqueur.commands.api.arguments.Arguments; +import fr.traqueur.commands.spigot.Command; +import fr.traqueur.morebees.api.BeePlugin; +import fr.traqueur.morebees.api.Messages; +import fr.traqueur.morebees.api.util.MiniMessageHelper; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; + +public class MoreBeesRootCommand extends Command<@NotNull BeePlugin> { + + public MoreBeesRootCommand(BeePlugin plugin) { + super(plugin, "morebees"); + + this.addAlias("bees", "mb", "bee"); + this.setPermission("morebees.command.help"); + this.addSubCommand( + new EggCommand(plugin), + new HoneyCommand(plugin), + new ReloadCommand(plugin), + new SpawnCommand(plugin), + new ToolCommand(plugin), + new UpgradeCommand(plugin) + ); + } + + @Override + public void execute(CommandSender sender, Arguments arguments) { + StringBuilder message = new StringBuilder(); + message.append(Messages.COMMAND_HELP_TITLE.raw() + .replace("%plugin%", this.getPlugin().getPluginMeta().getName()) + .replace("%authors%", String.join(", ", this.getPlugin().getPluginMeta().getAuthors())) + .replace("%version%", this.getPlugin().getPluginMeta().getVersion()) + ).append("\n"); + for (fr.traqueur.commands.api.models.Command<@NotNull BeePlugin, CommandSender> subcommand : this.getSubcommands()) { + if(subcommand.getPermission().isEmpty() || sender.hasPermission(subcommand.getPermission())) { + String usage = subcommand.getUsage(); + if (usage.isEmpty()) { + usage = subcommand.generateDefaultUsage(this.getPlugin().getCommandManager().getPlatform(), sender, this.getName()); + } + String formattedSyntax = Messages.COMMAND_SYNTAX.raw().replace("%usage%", usage).replace("%description%", subcommand.getDescription()); + message.append(formattedSyntax).append("\n"); + } + } + String formattedMessage = message.toString().trim(); + sender.sendMessage(MiniMessageHelper.parse(formattedMessage)); + } +} diff --git a/src/main/java/fr/traqueur/morebees/commands/ReloadCommand.java b/src/main/java/fr/traqueur/morebees/commands/ReloadCommand.java new file mode 100644 index 0000000..a984e24 --- /dev/null +++ b/src/main/java/fr/traqueur/morebees/commands/ReloadCommand.java @@ -0,0 +1,24 @@ +package fr.traqueur.morebees.commands; + +import fr.traqueur.commands.api.arguments.Arguments; +import fr.traqueur.commands.spigot.Command; +import fr.traqueur.morebees.api.BeePlugin; +import fr.traqueur.morebees.api.Messages; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; + +public class ReloadCommand extends Command<@NotNull BeePlugin> { + + public ReloadCommand(BeePlugin plugin) { + super(plugin, "reload"); + + this.setPermission("morebees.command.reload"); + this.setDescription(Messages.RELOAD_COMMAND_DESC.raw()); + } + + @Override + public void execute(CommandSender sender, Arguments arguments) { + this.getPlugin().reloadConfig(); + Messages.RELOAD_SUCCESS.send(sender); + } +} diff --git a/src/main/java/fr/traqueur/morebees/commands/SpawnCommand.java b/src/main/java/fr/traqueur/morebees/commands/SpawnCommand.java new file mode 100644 index 0000000..d3c2ce5 --- /dev/null +++ b/src/main/java/fr/traqueur/morebees/commands/SpawnCommand.java @@ -0,0 +1,45 @@ +package fr.traqueur.morebees.commands; + +import fr.traqueur.commands.api.arguments.Arguments; +import fr.traqueur.commands.spigot.Command; +import fr.traqueur.morebees.api.BeePlugin; +import fr.traqueur.morebees.api.Messages; +import fr.traqueur.morebees.api.managers.BeeManager; +import fr.traqueur.morebees.api.models.BeeType; +import fr.traqueur.morebees.api.util.Formatter; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; + +public class SpawnCommand extends Command<@NotNull BeePlugin> { + + public SpawnCommand(BeePlugin plugin) { + super(plugin, "spawn"); + + this.setPermission("morebees.command.spawn"); + this.setDescription(Messages.SPAWN_COMMAND_DESC.raw()); + + this.addArgs("beetype", BeeType.class); + this.addOptionalArgs("baby", Boolean.class); + this.addOptionalArgs("nectar", Boolean.class); + this.setGameOnly(true); + } + + @Override + public void execute(CommandSender sender, Arguments arguments) { + BeeType beeType = arguments.get("beetype"); + Player player = (Player) sender; + Optional babyOptional = arguments.getOptional("baby"); + Optional nectarOptional = arguments.getOptional("nectar"); + boolean nectar = nectarOptional.orElse(false); + boolean baby = babyOptional.orElse(false); + + BeeManager beeManager = getPlugin().getManager(BeeManager.class); + + beeManager.spawnBee(player.getLocation(), beeType, CreatureSpawnEvent.SpawnReason.COMMAND, baby, nectar); + Messages.SPAWN_COMMAND_SUCCESS.send(sender, Formatter.format("beetype", beeType.displayName())); + } +} diff --git a/src/main/java/fr/traqueur/morebees/commands/ToolCommand.java b/src/main/java/fr/traqueur/morebees/commands/ToolCommand.java new file mode 100644 index 0000000..7cf04e7 --- /dev/null +++ b/src/main/java/fr/traqueur/morebees/commands/ToolCommand.java @@ -0,0 +1,43 @@ +package fr.traqueur.morebees.commands; + +import fr.traqueur.commands.api.arguments.Arguments; +import fr.traqueur.commands.spigot.Command; +import fr.traqueur.morebees.api.BeePlugin; +import fr.traqueur.morebees.api.Messages; +import fr.traqueur.morebees.api.models.Tool; +import fr.traqueur.morebees.api.util.Formatter; +import fr.traqueur.morebees.api.util.Util; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class ToolCommand extends Command<@NotNull BeePlugin> { + /** + * The constructor of the command. + * + * @param plugin The plugin that owns the command. + */ + public ToolCommand(BeePlugin plugin) { + super(plugin, "tool"); + + this.setPermission("morebees.command.tool"); + this.setDescription(Messages.TOOL_COMMAND_DESC.raw()); + + this.addArgs("player", Player.class, "tool", Tool.class); + } + + @Override + public void execute(CommandSender sender, Arguments arguments) { + Player targetPlayer = arguments.get("player"); + + Tool tool = arguments.get("tool"); + ItemStack stack = tool.itemStack(List.of()); + + Util.giveItem(targetPlayer, stack); + + Messages.TOOL_COMMAND_SUCCESS.send(sender, Formatter.all("player", targetPlayer.getName(), "tool", tool.name())); + } +} diff --git a/src/main/java/fr/traqueur/morebees/commands/UpgradeCommand.java b/src/main/java/fr/traqueur/morebees/commands/UpgradeCommand.java new file mode 100644 index 0000000..ff70df4 --- /dev/null +++ b/src/main/java/fr/traqueur/morebees/commands/UpgradeCommand.java @@ -0,0 +1,40 @@ +package fr.traqueur.morebees.commands; + +import fr.traqueur.commands.api.arguments.Arguments; +import fr.traqueur.commands.spigot.Command; +import fr.traqueur.morebees.api.BeePlugin; +import fr.traqueur.morebees.api.Messages; +import fr.traqueur.morebees.api.models.Upgrade; +import fr.traqueur.morebees.api.util.Formatter; +import fr.traqueur.morebees.api.util.Util; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +public class UpgradeCommand extends Command<@NotNull BeePlugin> { + /** + * The constructor of the command. + * + * @param plugin The plugin that owns the command. + */ + public UpgradeCommand(BeePlugin plugin) { + super(plugin, "upgrade"); + + this.setPermission("morebees.command.upgrade"); + this.setDescription(Messages.UPGRADE_COMMAND_DESC.raw()); + + this.addArgs("player", Player.class, "upgrade", Upgrade.class); + } + + @Override + public void execute(CommandSender sender, Arguments arguments) { + Player targetPlayer = arguments.get("player"); + Upgrade upgrade = arguments.get("upgrade"); + ItemStack stack = upgrade.build(); + + Util.giveItem(targetPlayer, stack); + + Messages.UPGRADE_COMMAND_SUCCESS.send(sender, Formatter.all("player", targetPlayer.getName(), "upgrade", upgrade.id())); + } +} diff --git a/src/main/java/fr/traqueur/morebees/commands/arguments/BeeTypeArgument.java b/src/main/java/fr/traqueur/morebees/commands/arguments/BeeTypeArgument.java new file mode 100644 index 0000000..ab4f6b7 --- /dev/null +++ b/src/main/java/fr/traqueur/morebees/commands/arguments/BeeTypeArgument.java @@ -0,0 +1,30 @@ +package fr.traqueur.morebees.commands.arguments; + +import fr.traqueur.commands.api.arguments.ArgumentConverter; +import fr.traqueur.commands.api.arguments.TabCompleter; +import fr.traqueur.morebees.api.BeePlugin; +import fr.traqueur.morebees.api.models.BeeType; +import fr.traqueur.morebees.api.settings.GlobalSettings; +import org.bukkit.command.CommandSender; + +import java.util.List; +import java.util.stream.Collectors; + +public class BeeTypeArgument implements ArgumentConverter, TabCompleter { + + private final BeePlugin plugin; + + public BeeTypeArgument(BeePlugin plugin) { + this.plugin = plugin; + } + + @Override + public BeeType apply(String s) { + return this.plugin.getSettings(GlobalSettings.class).getBeeType(s).orElse(null); + } + + @Override + public List onCompletion(CommandSender sender, List args) { + return this.plugin.getSettings(GlobalSettings.class).bees().stream().map(BeeType::type).collect(Collectors.toList()); + } +} diff --git a/src/main/java/fr/traqueur/morebees/commands/arguments/ToolsArgument.java b/src/main/java/fr/traqueur/morebees/commands/arguments/ToolsArgument.java new file mode 100644 index 0000000..fcb6351 --- /dev/null +++ b/src/main/java/fr/traqueur/morebees/commands/arguments/ToolsArgument.java @@ -0,0 +1,27 @@ +package fr.traqueur.morebees.commands.arguments; + +import fr.traqueur.commands.api.arguments.ArgumentConverter; +import fr.traqueur.commands.api.arguments.TabCompleter; +import fr.traqueur.morebees.api.models.Tool; +import org.bukkit.command.CommandSender; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class ToolsArgument implements ArgumentConverter, TabCompleter { + + @Override + public Tool apply(String s) { + try { + return Tool.valueOf(s.toUpperCase()); + } catch (IllegalArgumentException e) { + return null; + } + } + + @Override + public List onCompletion(CommandSender sender, List args) { + return Arrays.stream(Tool.values()).map(Tool::name).collect(Collectors.toList()); + } +} diff --git a/src/main/java/fr/traqueur/morebees/commands/arguments/UpgradeArgument.java b/src/main/java/fr/traqueur/morebees/commands/arguments/UpgradeArgument.java new file mode 100644 index 0000000..f79d6e3 --- /dev/null +++ b/src/main/java/fr/traqueur/morebees/commands/arguments/UpgradeArgument.java @@ -0,0 +1,30 @@ +package fr.traqueur.morebees.commands.arguments; + +import fr.traqueur.commands.api.arguments.ArgumentConverter; +import fr.traqueur.commands.api.arguments.TabCompleter; +import fr.traqueur.morebees.api.BeePlugin; +import fr.traqueur.morebees.api.models.Upgrade; +import fr.traqueur.morebees.api.settings.UpgradeSettings; +import org.bukkit.command.CommandSender; + +import java.util.List; +import java.util.stream.Collectors; + +public class UpgradeArgument implements ArgumentConverter, TabCompleter { + + private final BeePlugin plugin; + + public UpgradeArgument(BeePlugin plugin) { + this.plugin = plugin; + } + + @Override + public Upgrade apply(String s) { + return this.plugin.getSettings(UpgradeSettings.class).getUpgrade(s).orElse(null); + } + + @Override + public List onCompletion(CommandSender sender, List args) { + return this.plugin.getSettings(UpgradeSettings.class).upgrades().stream().map(Upgrade::id).collect(Collectors.toList()); + } +} diff --git a/src/main/java/fr/traqueur/morebees/goals/BeePollinateGoal.java b/src/main/java/fr/traqueur/morebees/goals/BeePollinateGoal.java new file mode 100644 index 0000000..fc11fb1 --- /dev/null +++ b/src/main/java/fr/traqueur/morebees/goals/BeePollinateGoal.java @@ -0,0 +1,167 @@ +package fr.traqueur.morebees.goals; + +import com.destroystokyo.paper.entity.ai.Goal; +import com.destroystokyo.paper.entity.ai.GoalKey; +import com.destroystokyo.paper.entity.ai.GoalType; +import fr.traqueur.morebees.api.BeePlugin; +import org.bukkit.Location; +import org.bukkit.NamespacedKey; +import org.bukkit.block.Block; +import org.bukkit.entity.Bee; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; + +import java.util.EnumSet; +import java.util.function.Predicate; + +public class BeePollinateGoal implements Goal<@NotNull Bee> { + + private static final int SEARCH_RADIUS = 10; + + private final BeePlugin plugin; + private final Bee bee; + private final Predicate isFlower; + private Block targetFlower; + + private int pollinationTicks; + private int searchCooldown; + + public BeePollinateGoal(BeePlugin plugin, Bee bee, Predicate isFlower) { + this.bee = bee; + this.isFlower = isFlower; + this.plugin = plugin; + this.pollinationTicks = 0; + this.searchCooldown = 0; + } + + + @Override + public boolean shouldActivate() { + if (bee.hasNectar() || bee.getAnger() > 0) { + return false; + } + + // Cooldown de recherche + if (searchCooldown > 0) { + searchCooldown--; + return false; + } + + targetFlower = findNearestFlower(); + return targetFlower != null; + } + + + @Override + public boolean shouldStayActive() { + if (targetFlower == null) return false; + + if (!isFlower.test(targetFlower)) return false; + + if (pollinationTicks > 0) { + return true; + } + + return !bee.hasNectar(); + } + + @Override + public void start() { + pollinationTicks = 0; + } + + @Override + public void stop() { + targetFlower = null; + pollinationTicks = 0; + searchCooldown = 60; + } + + @Override + public void tick() { + if (targetFlower == null) return; + + Location flowerLoc = targetFlower.getLocation().add(0.5, 0.5, 0.5); + Location beeLoc = bee.getLocation(); + + double distance = beeLoc.distance(flowerLoc); + + if (distance > 2.0) { + moveToFlower(flowerLoc); + } else { + pollinateFlower(); + } + } + + private void moveToFlower(Location flowerLoc) { + Location beeLoc = bee.getLocation(); + Vector direction = flowerLoc.toVector().subtract(beeLoc.toVector()).normalize(); + Location target = flowerLoc.clone().add(direction.multiply(-0.5)).add(0, 0.5, 0); + bee.getPathfinder().moveTo(target, 1.0); + } + + private void pollinateFlower() { + pollinationTicks++; + + float rotationSpeed = 90f; + float currentBodyYaw = bee.getBodyYaw(); + float newBodyYaw = (currentBodyYaw + rotationSpeed) % 360f; + bee.setBodyYaw(newBodyYaw); + + if (pollinationTicks >= 100) { + bee.setHasNectar(true); + pollinationTicks = 0; + } + } + + private Block findNearestFlower() { + Location beeLoc = bee.getLocation(); + for (int radius = 1; radius <= SEARCH_RADIUS; radius++) { + Block found = searchAtRadius(beeLoc, radius); + if (found != null) { + return found; + } + } + return null; + } + + private Block searchAtRadius(Location center, int radius) { + // Recherche sur les faces du cube à ce rayon + int minY = Math.max(center.getBlockY() - radius/2, center.getWorld().getMinHeight()); + int maxY = Math.min(center.getBlockY() + radius/2, center.getWorld().getMaxHeight()); + + for (int y = minY; y <= maxY; y++) { + for (int x = -radius; x <= radius; x++) { + Block block1 = center.getWorld().getBlockAt( + center.getBlockX() + x, y, center.getBlockZ() + radius); + Block block2 = center.getWorld().getBlockAt( + center.getBlockX() + x, y, center.getBlockZ() - radius); + + if (isFlower.test(block1)) return block1; + if (isFlower.test(block2)) return block2; + } + + for (int z = -radius + 1; z < radius; z++) { + Block block1 = center.getWorld().getBlockAt( + center.getBlockX() + radius, y, center.getBlockZ() + z); + Block block2 = center.getWorld().getBlockAt( + center.getBlockX() - radius, y, center.getBlockZ() + z); + + if (isFlower.test(block1)) return block1; + if (isFlower.test(block2)) return block2; + } + } + + return null; + } + + @Override + public @NotNull GoalKey<@NotNull Bee> getKey() { + return GoalKey.of(Bee.class, new NamespacedKey(plugin, "pollinate")); + } + + @Override + public @NotNull EnumSet getTypes() { + return EnumSet.of(GoalType.MOVE, GoalType.LOOK); + } +} diff --git a/src/main/java/fr/traqueur/morebees/goals/BeeTemptGoal.java b/src/main/java/fr/traqueur/morebees/goals/BeeTemptGoal.java new file mode 100644 index 0000000..cceaf0e --- /dev/null +++ b/src/main/java/fr/traqueur/morebees/goals/BeeTemptGoal.java @@ -0,0 +1,125 @@ +package fr.traqueur.morebees.goals; + +import com.destroystokyo.paper.entity.ai.Goal; +import com.destroystokyo.paper.entity.ai.GoalKey; +import com.destroystokyo.paper.entity.ai.GoalType; +import fr.traqueur.morebees.api.BeePlugin; +import org.bukkit.Location; +import org.bukkit.NamespacedKey; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeInstance; +import org.bukkit.entity.Bee; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.EnumSet; +import java.util.function.Predicate; + +public class BeeTemptGoal implements Goal<@NotNull Bee> { + + private final BeePlugin plugin; + private final Bee bee; + private final double speedModifier; + private final Predicate items; + private Player targetPlayer; + private int calmDown; + + public BeeTemptGoal(BeePlugin plugin, Bee bee, double speedModifier, Predicate items) { + this.bee = bee; + this.plugin = plugin; + this.speedModifier = speedModifier; + this.items = items; + this.calmDown = 0; + } + + + @Override + public boolean shouldActivate() { + if (this.calmDown > 0) { + --this.calmDown; + return false; + } + + this.targetPlayer = findNearestTemptingPlayer(); + return this.targetPlayer != null; + } + + @Override + public boolean shouldStayActive() { + if (this.targetPlayer == null) { + return false; + } + + return this.shouldActivate(); + } + + @Override + public void stop() { + this.targetPlayer = null; + this.bee.getPathfinder().stopPathfinding(); + this.calmDown = 100; + } + + @Override + public void tick() { + if (this.targetPlayer == null) return; + + Location mobLoc = this.bee.getLocation(); + Location playerLoc = this.targetPlayer.getLocation(); + + this.bee.lookAt(playerLoc, 30.0F, 30.0F); + + if (mobLoc.distanceSquared(playerLoc) < 6.25) { + this.bee.getPathfinder().stopPathfinding(); + } else { + this.bee.getPathfinder().moveTo(playerLoc, this.speedModifier); + } + } + + private Player findNearestTemptingPlayer() { + return this.bee.getWorld() + .getNearbyPlayers(this.bee.getLocation(), this.getTemptRangeFromEntity()) + .stream() + .filter(this::shouldFollowPlayer) + .min((p1, p2) -> Double.compare( + p1.getLocation().distanceSquared(this.bee.getLocation()), + p2.getLocation().distanceSquared(this.bee.getLocation()) + )) + .orElse(null); + } + + @Override + public @NotNull GoalKey<@NotNull Bee> getKey() { + return GoalKey.of(Bee.class, new NamespacedKey(plugin, "tempt")); + } + + @Override + public @NotNull EnumSet getTypes() { + return EnumSet.of(GoalType.MOVE, GoalType.LOOK); + } + + private boolean shouldFollowPlayer(Player player) { + ItemStack mainHand = player.getInventory().getItemInMainHand(); + ItemStack offHand = player.getInventory().getItemInOffHand(); + + return this.items.test(mainHand) || this.items.test(offHand); + } + + private double getTemptRangeFromEntity() { + if (this.bee instanceof org.bukkit.attribute.Attributable attributable) { + AttributeInstance temptRangeAttr = attributable.getAttribute(Attribute.TEMPT_RANGE); + if (temptRangeAttr != null) { + return temptRangeAttr.getValue(); + } + } + return 10.0; + } + + private double distanceSquared(double x1, double y1, double z1, double x2, double y2, double z2) { + double dx = x1 - x2; + double dy = y1 - y2; + double dz = z1 - z2; + return dx * dx + dy * dy + dz * dz; + } +} diff --git a/src/main/java/fr/traqueur/morebees/hooks/Hooks.java b/src/main/java/fr/traqueur/morebees/hooks/Hooks.java new file mode 100644 index 0000000..c425c5a --- /dev/null +++ b/src/main/java/fr/traqueur/morebees/hooks/Hooks.java @@ -0,0 +1,74 @@ +package fr.traqueur.morebees.hooks; + +import fr.traqueur.morebees.api.BeePlugin; +import fr.traqueur.morebees.api.Logger; +import fr.traqueur.morebees.api.hooks.Hook; +import fr.traqueur.morebees.hooks.modelengine.ModelEngineHook; +import org.bukkit.Bukkit; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.function.Supplier; + +public enum Hooks { + + MODEL_ENGINE("ModelEngine", ModelEngineHook::new), + ITEMS_ADDER("ItemsAdder", ItemsAdderHook::new), + ORAXEN("Oraxen", OraxenHook::new), + NEXO("Nexo", NexoHook::new), + ; + + private final String pluginName; + private final Supplier hookSupplier; + private Hook hook; + + private static final Set TO_RETRY = new HashSet<>(); + + Hooks(String pluginName, Supplier hookSupplier) { + this.pluginName = pluginName; + this.hookSupplier = hookSupplier; + } + + + public Optional get() { + //noinspection unchecked + return Optional.ofNullable((T) hook); + } + + private boolean init() { + if (hook == null && Bukkit.getPluginManager().getPlugin(pluginName) != null) { + hook = hookSupplier.get(); + return true; + } + return false; + } + + public static void initAll(BeePlugin plugin) { + for (Hooks hooks : Hooks.values()) { + if (hooks.init()) { + hooks.get().ifPresent(hook -> enableHook(hooks.pluginName, hook)); + } else { + TO_RETRY.add(hooks); + } + } + Bukkit.getScheduler().runTask(plugin, () -> { + for (Hooks hooks : TO_RETRY) { + if (hooks.init()) { + hooks.get().ifPresent(hook -> enableHook(hooks.pluginName, hook)); + } else { + Logger.debug("{} hook failed to initialize...", hooks.pluginName); + } + } + TO_RETRY.clear(); + }); + } + + private static void enableHook(String hookName, Hook hook) { + try { + Hook.register(hook); + } catch (Exception e) { + Logger.severe("Failed to enable hook for {}",e, hookName); + } + } +} diff --git a/src/main/java/fr/traqueur/morebees/hooks/ItemsAdderHook.java b/src/main/java/fr/traqueur/morebees/hooks/ItemsAdderHook.java new file mode 100644 index 0000000..6208a23 --- /dev/null +++ b/src/main/java/fr/traqueur/morebees/hooks/ItemsAdderHook.java @@ -0,0 +1,44 @@ +package fr.traqueur.morebees.hooks; + +import dev.lone.itemsadder.api.CustomBlock; +import dev.lone.itemsadder.api.CustomStack; +import fr.traqueur.morebees.api.BeePlugin; +import fr.traqueur.morebees.api.Logger; +import fr.traqueur.morebees.api.hooks.ItemProviderHook; +import org.bukkit.block.Block; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; + +public class ItemsAdderHook implements ItemProviderHook { + @Override + public @Nullable String getItemName(ItemStack item) { + CustomStack stack = CustomStack.byItemStack(item); + if(stack == null) { + return null; + } + return stack.getNamespacedID(); + } + + @Override + public @Nullable String getBlockName(Block block) { + CustomBlock customBlock = CustomBlock.byAlreadyPlaced(block); + if(customBlock != null) { + return customBlock.getNamespacedID(); + } + return null; + } + + @Override + public ItemStack getItemFromId(String product) { + CustomStack stack = CustomStack.getInstance(product); + if(stack == null) { + return null; + } + return stack.getItemStack(); + } + + @Override + public void onEnable(BeePlugin plugin) { + Logger.success("ItemsAdder hook enabled successfully!"); + } +} diff --git a/src/main/java/fr/traqueur/morebees/hooks/NexoHook.java b/src/main/java/fr/traqueur/morebees/hooks/NexoHook.java new file mode 100644 index 0000000..66b1886 --- /dev/null +++ b/src/main/java/fr/traqueur/morebees/hooks/NexoHook.java @@ -0,0 +1,42 @@ +package fr.traqueur.morebees.hooks; + +import com.nexomc.nexo.api.NexoItems; +import com.nexomc.nexo.items.ItemBuilder; +import com.nexomc.nexo.mechanics.custom_block.CustomBlockMechanic; +import com.nexomc.nexo.mechanics.custom_block.CustomBlockRegistry; +import fr.traqueur.morebees.api.BeePlugin; +import fr.traqueur.morebees.api.Logger; +import fr.traqueur.morebees.api.hooks.ItemProviderHook; +import org.bukkit.block.Block; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; + +public class NexoHook implements ItemProviderHook { + @Override + public @Nullable String getItemName(ItemStack item) { + return NexoItems.idFromItem(item); + } + + @Override + public @Nullable String getBlockName(Block block) { + CustomBlockMechanic blockMechanic = CustomBlockRegistry.INSTANCE.getMechanic(block); + if (blockMechanic != null) { + return blockMechanic.getItemID(); + } + return null; + } + + @Override + public ItemStack getItemFromId(String product) { + ItemBuilder builder = NexoItems.itemFromId(product); + if (builder == null) { + return null; + } + return builder.build(); + } + + @Override + public void onEnable(BeePlugin plugin) { + Logger.success("ItemsAdder hook enabled successfully!"); + } +} diff --git a/src/main/java/fr/traqueur/morebees/hooks/OraxenHook.java b/src/main/java/fr/traqueur/morebees/hooks/OraxenHook.java new file mode 100644 index 0000000..2ed8ae8 --- /dev/null +++ b/src/main/java/fr/traqueur/morebees/hooks/OraxenHook.java @@ -0,0 +1,47 @@ +package fr.traqueur.morebees.hooks; + +import fr.traqueur.morebees.api.BeePlugin; +import fr.traqueur.morebees.api.Logger; +import fr.traqueur.morebees.api.hooks.ItemProviderHook; +import io.th0rgal.oraxen.api.OraxenBlocks; +import io.th0rgal.oraxen.api.OraxenItems; +import io.th0rgal.oraxen.items.ItemBuilder; +import io.th0rgal.oraxen.mechanics.provided.gameplay.block.BlockMechanic; +import io.th0rgal.oraxen.mechanics.provided.gameplay.noteblock.NoteBlockMechanic; +import org.bukkit.block.Block; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; + +public class OraxenHook implements ItemProviderHook { + @Override + public @Nullable String getItemName(ItemStack item) { + return OraxenItems.getIdByItem(item); + } + + @Override + public @Nullable String getBlockName(Block block) { + BlockMechanic blockMechanic = OraxenBlocks.getBlockMechanic(block); + if (blockMechanic != null) { + return blockMechanic.getItemID(); + } + NoteBlockMechanic noteBlockMechanic = OraxenBlocks.getNoteBlockMechanic(block); + if (noteBlockMechanic != null) { + return noteBlockMechanic.getItemID(); + } + return null; + } + + @Override + public ItemStack getItemFromId(String product) { + ItemBuilder builder = OraxenItems.getItemById(product); + if (builder == null) { + return null; + } + return builder.build(); + } + + @Override + public void onEnable(BeePlugin plugin) { + Logger.success("ItemsAdder hook enabled successfully!"); + } +} diff --git a/src/main/java/fr/traqueur/morebees/hooks/modelengine/BeeGrowWatcher.java b/src/main/java/fr/traqueur/morebees/hooks/modelengine/BeeGrowWatcher.java new file mode 100644 index 0000000..63e7c2b --- /dev/null +++ b/src/main/java/fr/traqueur/morebees/hooks/modelengine/BeeGrowWatcher.java @@ -0,0 +1,44 @@ +package fr.traqueur.morebees.hooks.modelengine; + +import fr.traqueur.morebees.api.BeePlugin; +import org.bukkit.Bukkit; +import org.bukkit.entity.Bee; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +public class BeeGrowWatcher implements Runnable{ + + private final BeePlugin plugin; + private final ModelEngineHook hook; + private final List> trackedBees; + + public BeeGrowWatcher(BeePlugin plugin, ModelEngineHook hook) { + this.plugin = plugin; + this.hook = hook; + this.trackedBees = new ArrayList<>(); + } + + public void track(Bee bee) { + this.trackedBees.add(new WeakReference<>(bee)); + } + + @Override + public void run() { + Iterator> iterator = this.trackedBees.iterator(); + while (iterator.hasNext()) { + Bee bee = iterator.next().get(); + if (bee == null || !bee.isValid()) { + iterator.remove(); + continue; + } + if(!bee.isAdult()) { + continue; + } + iterator.remove(); + Bukkit.getScheduler().runTask(this.plugin,() -> hook.grow(bee)); + } + } +} diff --git a/src/main/java/fr/traqueur/morebees/hooks/modelengine/ModelEngineHook.java b/src/main/java/fr/traqueur/morebees/hooks/modelengine/ModelEngineHook.java new file mode 100644 index 0000000..ea80d71 --- /dev/null +++ b/src/main/java/fr/traqueur/morebees/hooks/modelengine/ModelEngineHook.java @@ -0,0 +1,83 @@ +package fr.traqueur.morebees.hooks.modelengine; + +import com.ticxo.modelengine.api.ModelEngineAPI; +import com.ticxo.modelengine.api.animation.handler.AnimationHandler; +import com.ticxo.modelengine.api.animation.property.IAnimationProperty; +import com.ticxo.modelengine.api.model.ActiveModel; +import com.ticxo.modelengine.api.model.ModeledEntity; +import fr.traqueur.morebees.api.BeePlugin; +import fr.traqueur.morebees.api.Logger; +import fr.traqueur.morebees.api.hooks.Hook; +import fr.traqueur.morebees.api.models.BeeType; +import fr.traqueur.morebees.api.serialization.Keys; +import fr.traqueur.morebees.api.serialization.datas.BeeTypeDataType; +import fr.traqueur.morebees.api.settings.GlobalSettings; +import org.bukkit.entity.Bee; +import org.bukkit.persistence.PersistentDataContainer; + +public class ModelEngineHook implements Hook { + + private BeePlugin plugin; + private BeeGrowWatcher beeGrowWatcher; + + @Override + public void onEnable(BeePlugin plugin) { + this.plugin = plugin; + this.beeGrowWatcher = new BeeGrowWatcher(plugin,this); + plugin.getServer().getScheduler().runTaskTimerAsynchronously(plugin, this.beeGrowWatcher, 20L, 100L); + Logger.success("ModelEngine hook enabled successfully!"); + } + + public boolean overrideModel(Bee entity, BeeType beeType) { + if(beeType.equals(BeeType.NORMAL)) { + Logger.warning("Cannot override model for the normal bee type, skipping entity {}", entity.getUniqueId()); + return false; + } + if(beeType.model() == null) { + return false; + } + + if (ModelEngineAPI.getAPI().getModelRegistry().get(beeType.model()) == null) { + Logger.warning("The model {} does not exist, skipping model override for entity {}", beeType.model(), entity.getUniqueId()); + return false; + } + + entity.setInvisible(true); + + ModeledEntity modeledEntity = ModelEngineAPI.createModeledEntity(entity); + ActiveModel activeModel = ModelEngineAPI.createActiveModel(beeType.model()); + + AnimationHandler animationHandler = activeModel.getAnimationHandler(); + IAnimationProperty playedAnimation = animationHandler.playAnimation(this.plugin.getSettings(GlobalSettings.class).flyAnimation(), 0.3, 0.3, 1, true); + if(playedAnimation == null) { + Logger.warning("Failed to play animation {} for model {} on entity {}", this.plugin.getSettings(GlobalSettings.class).flyAnimation(), beeType.model(), entity.getUniqueId()); + } + + if (!entity.isAdult()) { + activeModel.setScale(0.5f); + beeGrowWatcher.track(entity); + } + + modeledEntity.addModel(activeModel, true); + return true; + } + + public void grow(Bee entity) { + PersistentDataContainer data = entity.getPersistentDataContainer(); + Keys.BEE_TYPE.get(data, BeeTypeDataType.INSTANCE).ifPresent(beeType -> { + if(beeType.model() == null) { + return; + } + + ModeledEntity modeledEntity = ModelEngineAPI.getModeledEntity(entity); + if (modeledEntity == null) { + return; + } + modeledEntity.getModel(beeType.model()).ifPresent(beeModel -> { + beeModel.setScale(1f); + }); + }); + } + + +} diff --git a/src/main/java/fr/traqueur/morebees/listeners/BeeListener.java b/src/main/java/fr/traqueur/morebees/listeners/BeeListener.java new file mode 100644 index 0000000..4571c0a --- /dev/null +++ b/src/main/java/fr/traqueur/morebees/listeners/BeeListener.java @@ -0,0 +1,171 @@ +package fr.traqueur.morebees.listeners; + +import fr.traqueur.morebees.api.BeePlugin; +import fr.traqueur.morebees.api.managers.BeeManager; +import fr.traqueur.morebees.api.models.BeeType; +import fr.traqueur.morebees.api.settings.BreedSettings; +import fr.traqueur.morebees.api.util.Util; +import io.papermc.paper.event.entity.EntityMoveEvent; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.entity.Bee; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.event.entity.EntityBreedEvent; +import org.bukkit.event.player.PlayerInteractEntityEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.world.EntitiesLoadEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; + +import java.util.Optional; + +public class BeeListener implements Listener { + + private final BeePlugin plugin; + + public BeeListener(BeePlugin plugin) { + this.plugin = plugin; + } + + @EventHandler + public void onInteract(PlayerInteractEvent event) { + if (event.getAction() != Action.RIGHT_CLICK_BLOCK) { + return; + } + + Block clickedBlock = event.getClickedBlock(); + if (clickedBlock == null) { + return; + } + ItemStack item = event.getItem(); + if (item == null) { + return; + } + + Block spawnBlock = clickedBlock.getRelative(event.getBlockFace()); + Location spawnLocation = spawnBlock.getLocation().add(0.5, 0, 0.5); + + this.handleSpawn(event, event.getPlayer(), item, spawnLocation, false); + } + + @EventHandler + public void onInteract(PlayerInteractEntityEvent event) { + if(!(event.getRightClicked() instanceof Bee bee)) { + return; + } + if(EquipmentSlot.OFF_HAND == event.getHand()) { + return; + } + ItemStack item = event.getPlayer().getInventory().getItemInMainHand(); + Location spawnLocation = event.getRightClicked().getLocation(); + BeeManager beeManager = plugin.getManager(BeeManager.class); + Optional eggBeeType = beeManager.getBeeTypeFromEgg(item); + if (eggBeeType.isPresent()) { + this.handleSpawn(event, event.getPlayer(), item, spawnLocation, true); + return; + } + + Optional entityType = beeManager.getBeeTypeFromEntity(bee); + entityType.ifPresent(beeType -> { + event.setCancelled(true); + if(!beeType.isFood(item)) { + return; + } + if (event.getPlayer().getGameMode() != GameMode.CREATIVE) { + item.setAmount(item.getAmount() - 1); + } + beeManager.feed(event.getPlayer(), bee); + }); + } + + private void handleSpawn(Cancellable event, Player player, ItemStack item, Location spawnLocation, boolean baby) { + BeeManager beeManager = plugin.getManager(BeeManager.class); + Optional beeType = beeManager.getBeeTypeFromEgg(item); + + if (beeType.isPresent()) { + event.setCancelled(true); + beeManager.spawnBee(spawnLocation, beeType.get(), CreatureSpawnEvent.SpawnReason.SPAWNER_EGG, baby, false); + + if (player.getGameMode() != GameMode.CREATIVE) { + item.setAmount(item.getAmount() - 1); + } + } + } + + @EventHandler + public void onBeesLoad(EntitiesLoadEvent event) { + BeeManager beeManager = plugin.getManager(BeeManager.class); + event.getEntities().stream() + .filter(entity -> entity.getType() == EntityType.BEE) + .map(entity -> (Bee) entity) + .forEach(bee -> { + beeManager.getBeeTypeFromEntity(bee).ifPresent(beeType -> { + beeManager.patchBee(bee, beeType); + }); + }); + } + + @EventHandler + public void onSpawn(CreatureSpawnEvent event) { + Entity entity = event.getEntity(); + if(entity.getType() != EntityType.BEE) { + return; + } + if(!(entity instanceof Bee bee)) { + return; + } + + BeeManager beeManager = plugin.getManager(BeeManager.class); + beeManager.getBeeTypeFromEntity(bee).ifPresent(beeType -> { + beeManager.patchBee(bee, beeType); + }); + } + + @EventHandler + public void onBreed(EntityBreedEvent event) { + BeeManager beeManager = plugin.getManager(BeeManager.class); + Optional motherType = beeManager.getBeeTypeFromEntity(event.getMother()); + Optional fatherType = beeManager.getBeeTypeFromEntity(event.getFather()); + + if(!(event.getEntity() instanceof Bee bee)) { + return; + } + + Util.ifBothPresent(motherType, fatherType, (mother, father) -> { + BeeType child = beeManager.computeChildType(mother, father); + beeManager.patchBee(bee, child); + }); + } + + + @EventHandler + public void onMove(EntityMoveEvent event) { + if(!(event.getEntity() instanceof Bee bee)) { + return; + } + + if(!bee.hasNectar()) { + return; + } + + Location to = event.getTo().clone().subtract(0,1,0); + if(to.getBlock().isEmpty()) { + return; + } + BeeManager beeManager = this.plugin.getManager(BeeManager.class); + beeManager.getBeeTypeFromEntity(bee). + flatMap(beeType -> this.plugin.getSettings(BreedSettings.class).getMutation(beeType, to.getBlock())) + .ifPresent(mutation -> { + beeManager.mutate(bee, mutation, to); + }); + } + +} diff --git a/src/main/java/fr/traqueur/morebees/listeners/BeehiveListener.java b/src/main/java/fr/traqueur/morebees/listeners/BeehiveListener.java new file mode 100644 index 0000000..4934729 --- /dev/null +++ b/src/main/java/fr/traqueur/morebees/listeners/BeehiveListener.java @@ -0,0 +1,146 @@ +package fr.traqueur.morebees.listeners; + +import fr.traqueur.morebees.api.BeePlugin; +import fr.traqueur.morebees.api.Logger; +import fr.traqueur.morebees.api.managers.BeeManager; +import fr.traqueur.morebees.api.managers.BeehiveManager; +import fr.traqueur.morebees.api.managers.UpgradesManager; +import fr.traqueur.morebees.api.models.BeeType; +import fr.traqueur.morebees.api.util.Util; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.block.Beehive; +import org.bukkit.block.Block; +import org.bukkit.entity.Bee; +import org.bukkit.entity.Item; +import org.bukkit.entity.ItemDisplay; +import org.bukkit.event.Event; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.block.BlockDropItemEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.entity.EntityChangeBlockEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.BlockStateMeta; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class BeehiveListener implements Listener { + + private final BeePlugin plugin; + + public BeehiveListener(BeePlugin plugin) { + this.plugin = plugin; + } + + @EventHandler + public void onDrop(BlockDropItemEvent event) { + BeehiveManager beehiveManager = this.plugin.getManager(BeehiveManager.class); + beehiveManager.getBeehiveFromBlock(event.getBlockState()).ifPresent(beehive -> { + for (Item item : event.getItems()) { + ItemStack itemStack = item.getItemStack(); + if(itemStack.getItemMeta() instanceof BlockStateMeta blockStateMeta && blockStateMeta.getBlockState() instanceof Beehive) { + item.setItemStack(beehive.patch(itemStack)); + Logger.debug("Dropped beehive at {}", item.getLocation()); + } + } + }); + } + + @EventHandler + public void onPlace(BlockPlaceEvent event) { + BeehiveManager beehiveManager = this.plugin.getManager(BeehiveManager.class); + UpgradesManager upgradesManager = this.plugin.getManager(UpgradesManager.class); + ItemStack itemInHand = event.getItemInHand(); + + Block block = event.getBlockPlaced(); + + beehiveManager.getBeehiveFromItem(itemInHand).ifPresent(beehive -> { + beehiveManager.saveBeehiveToBlock(block, beehive); + + ItemDisplay display = upgradesManager.createUpgradeDisplay(block, beehive.getUpgrade()); + UUID displayUUID = display == null ? null : display.getUniqueId(); + beehiveManager.editBeehive(block, beehiveToEdit -> { + beehiveToEdit.setUpgradeId(displayUUID); + }); + + Logger.debug("Placed beehive at {}", block.getLocation()); + }); + } + + + @EventHandler + public void onEntityChangeBlockEvent(EntityChangeBlockEvent event) { + BeeManager beeManager = this.plugin.getManager(BeeManager.class); + BeehiveManager beehiveManager = this.plugin.getManager(BeehiveManager.class); + + if(!(event.getEntity() instanceof Bee bee)) { + return; + } + + if(!(event.getBlock().getState() instanceof org.bukkit.block.Beehive beehiveState)) { + return; + } + + if(!(beehiveState.getBlockData() instanceof org.bukkit.block.data.type.Beehive beehiveData)) { + return; + } + + if(beehiveData.getHoneyLevel() > beehiveData.getMaximumHoneyLevel()) { + return; + } + + Bukkit.getScheduler().runTaskLater(this.plugin, () -> { + Util.ifBothPresent(beehiveManager.getBeehiveFromBlock(event.getBlock().getState()), beeManager.getBeeTypeFromEntity(bee), (beehive, beeType) -> { + beehive.addHoney(beeType, 1); + Logger.debug("Bee {} added {} honey to beehive at {}", bee.getUniqueId(), beeType.displayName(), event.getBlock().getLocation()); + beehiveManager.saveBeehiveToBlock(event.getBlock(), beehive); + }); + }, 1L); + } + + @EventHandler + public void onPlayerInteractEvent(PlayerInteractEvent event) { + if(event.getClickedBlock() == null) return; + if(event.getHand() != EquipmentSlot.HAND) return; + if(event.getAction() != Action.RIGHT_CLICK_BLOCK) return; + + if(event.getPlayer().getInventory().getItemInMainHand().getType() != Material.SHEARS) return; + if(!(event.getClickedBlock().getState() instanceof org.bukkit.block.Beehive beehiveBlock)) return; + if(!(event.getClickedBlock().getBlockData() instanceof org.bukkit.block.data.type.Beehive beehiveData)) return; + + if(beehiveData.getHoneyLevel() < beehiveData.getMaximumHoneyLevel()) { + return; + } + + BeehiveManager beehiveManager = this.plugin.getManager(BeehiveManager.class); + + event.setUseInteractedBlock(Event.Result.DENY); + + beehiveManager.editBeehive(event.getClickedBlock(), beehive -> { + Map honeyCombCounts = new HashMap<>(beehive.getHoneyCombCounts()); + honeyCombCounts.forEach((beeType, count) -> { + ItemStack template = beeType.honey(1, false); + int realAmount = (int) Math.floor(count * beehive.getUpgrade().productionMultiplier()); + Logger.debug("Dropping honey combs for bee type {}: {}", beeType.type(), realAmount); + while(realAmount > 0) { + int amount = Math.min(count, template.getMaxStackSize()); + ItemStack honeyComb = beeType.honey(amount, beehive.getUpgrade().produceBlocks()); + beehive.removeHoney(beeType, amount); + event.getPlayer().getWorld().dropItemNaturally(event.getPlayer().getLocation(), honeyComb); + realAmount -= amount; + } + }); + }); + + beehiveData.setHoneyLevel(0); + beehiveBlock.setBlockData(beehiveData); + beehiveBlock.update(); + } + +} diff --git a/src/main/java/fr/traqueur/morebees/listeners/ToolsListener.java b/src/main/java/fr/traqueur/morebees/listeners/ToolsListener.java new file mode 100644 index 0000000..d9a7dbc --- /dev/null +++ b/src/main/java/fr/traqueur/morebees/listeners/ToolsListener.java @@ -0,0 +1,110 @@ +package fr.traqueur.morebees.listeners; + +import fr.traqueur.morebees.api.BeePlugin; +import fr.traqueur.morebees.api.Logger; +import fr.traqueur.morebees.api.Messages; +import fr.traqueur.morebees.api.managers.BeeManager; +import fr.traqueur.morebees.api.managers.ToolsManager; +import fr.traqueur.morebees.api.models.BeeData; +import fr.traqueur.morebees.api.models.Tool; +import fr.traqueur.morebees.api.util.Formatter; +import fr.traqueur.morebees.api.util.MiniMessageHelper; +import fr.traqueur.morebees.api.util.Util; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Bee; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.event.player.PlayerInteractEntityEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; + +import java.util.*; + +public class ToolsListener implements Listener { + + private final BeePlugin plugin; + private final Set catchers; + + public ToolsListener(BeePlugin plugin) { + this.plugin = plugin; + this.catchers = new HashSet<>(); + } + + @EventHandler + public void onInteract(PlayerInteractEvent event) { + if (event.getHand() != EquipmentSlot.HAND) { + return; + } + + if(this.catchers.contains(event.getPlayer().getUniqueId())) { + return; + } + + if(!event.getAction().isRightClick()) { + return; + } + + Location location = event.getClickedBlock() != null ? event.getClickedBlock().getLocation() : event.getPlayer().getLocation(); + if (event.getClickedBlock() != null) { + location = location.add(event.getBlockFace().getDirection()); + } + location = location.add(0.5, 0, 0.5); + + Location finalLocation = location; + ItemStack toolItem = event.getPlayer().getInventory().getItemInMainHand(); + ToolsManager toolsManager = plugin.getManager(ToolsManager.class); + BeeManager beeManager = plugin.getManager(BeeManager.class); + + toolsManager.getTool(toolItem).ifPresent(tool -> { + List released = toolsManager.releaseBee(toolItem, event.getPlayer().isSneaking()); + for (BeeData beeData : released) { + beeManager.spawnBee(finalLocation, beeData.type(), CreatureSpawnEvent.SpawnReason.CUSTOM, !beeData.isAdult(), beeData.hasNectar()); + Logger.debug("Released bee of type {} with tool {} at location {}", + beeData.type().type(), + tool.name(), + finalLocation); + } + }); + + } + + @EventHandler + public void onEntityInteract(PlayerInteractEntityEvent event) { + BeeManager beeManager = plugin.getManager(BeeManager.class); + ToolsManager toolsManager = plugin.getManager(ToolsManager.class); + + if (event.getHand() != EquipmentSlot.HAND) { + return; + } + + if(!(event.getRightClicked() instanceof Bee bee)) { + return; + } + + ItemStack toolItem = event.getPlayer().getInventory().getItemInMainHand(); + Optional toolOpt = toolsManager.getTool(toolItem); + if (toolOpt.isEmpty()) { + return; + } + + if(toolsManager.isFull(toolItem)) { + Messages.TOOL_FULL.send(event.getPlayer(), Formatter.format("tool", MiniMessageHelper.unparse(toolItem.getItemMeta().itemName()))); + return; + } + + Util.ifBothPresent(beeManager.getBeeTypeFromEntity(bee), toolOpt, (beeType, tool) -> { + toolsManager.catchBee(toolItem, bee, beeType); + this.catchers.add(event.getPlayer().getUniqueId()); + Bukkit.getScheduler().runTaskLater(this.plugin, () -> this.catchers.remove(event.getPlayer().getUniqueId()), 5L); + event.setCancelled(true); + Logger.debug("Caught bee {} of type {} with tool {}", + bee.getUniqueId(), + beeType.type(), + tool.name()); + }); + } + +} diff --git a/src/main/java/fr/traqueur/morebees/listeners/UpgradesListener.java b/src/main/java/fr/traqueur/morebees/listeners/UpgradesListener.java new file mode 100644 index 0000000..814ed5d --- /dev/null +++ b/src/main/java/fr/traqueur/morebees/listeners/UpgradesListener.java @@ -0,0 +1,133 @@ +package fr.traqueur.morebees.listeners; + +import fr.traqueur.morebees.api.BeePlugin; +import fr.traqueur.morebees.api.Logger; +import fr.traqueur.morebees.api.managers.BeehiveManager; +import fr.traqueur.morebees.api.managers.UpgradesManager; +import fr.traqueur.morebees.api.models.Beehive; +import fr.traqueur.morebees.api.models.Upgrade; +import fr.traqueur.morebees.api.util.Util; +import org.bukkit.GameMode; +import org.bukkit.block.Block; +import org.bukkit.entity.ItemDisplay; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.world.ChunkLoadEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; + +import java.util.Arrays; +import java.util.Optional; +import java.util.UUID; + +public class UpgradesListener implements Listener { + + private final BeePlugin plugin; + + public UpgradesListener(BeePlugin plugin) { + this.plugin = plugin; + } + + @EventHandler + public void onInteract(PlayerInteractEvent event) { + UpgradesManager upgradesManager = this.plugin.getManager(UpgradesManager.class); + BeehiveManager beehiveManager = this.plugin.getManager(BeehiveManager.class); + if(event.getHand() != EquipmentSlot.HAND) return; + if(event.getAction() != Action.RIGHT_CLICK_BLOCK) return; + + Player player = event.getPlayer(); + Block clickedBlock = event.getClickedBlock(); + if (clickedBlock == null) return; + + if(!(clickedBlock.getState() instanceof org.bukkit.block.Beehive beehiveState)) { + return; // Not a beehive block + } + if(!(beehiveState.getBlockData() instanceof org.bukkit.block.data.type.Beehive beehiveData)) { + return; // Not a beehive block + } + + if (!beehiveData.getFacing().equals(event.getBlockFace())) { + return; // Not facing the beehive + } + + Optional beehiveOptional = beehiveManager.getBeehiveFromBlock(clickedBlock.getState()); + if (beehiveOptional.isEmpty()) return; + Beehive beehive = beehiveOptional.get(); + + Upgrade upgrade = beehive.getUpgrade(); + UUID currentDisplayUUID = beehive.getUpgradeId(); + + if(player.isSneaking() && !upgrade.equals(Upgrade.NONE)) { + event.setCancelled(true); + //only remove upgrade if player is sneaking + ItemStack toGive = upgrade.build(); + Util.giveItem(player, toGive); + beehiveManager.editBeehive(event.getClickedBlock(), beehiveToEdit -> { + beehiveToEdit.setUpgrade(Upgrade.NONE); + beehiveToEdit.setUpgradeId(null); + }); + + upgradesManager.removeUpgradeDisplay(currentDisplayUUID); + + Logger.debug("Removed upgrade {} from beehive at {}", upgrade.id(), clickedBlock.getLocation()); + return; + } + + ItemStack upgradeItem = event.getItem(); + upgradesManager.getUpgradeFromItem(upgradeItem).ifPresent(toAdd -> { + event.setCancelled(true); + if(toAdd.id().equals(upgrade.id())) { + return; + } + + if(!upgrade.equals(Upgrade.NONE)) { + ItemStack toGive = upgrade.build(); + Util.giveItem(player, toGive); + } + + if(player.getGameMode() != GameMode.CREATIVE && upgradeItem != null) { + upgradeItem.setAmount(upgradeItem.getAmount() - 1); + } + + ItemDisplay newDisplay = upgradesManager.createUpgradeDisplay(clickedBlock, toAdd); + UUID newDisplayUUID = newDisplay == null ? null : newDisplay.getUniqueId(); + + beehiveManager.editBeehive(event.getClickedBlock(), beehiveToEdit -> { + beehiveToEdit.setUpgrade(toAdd); + beehiveToEdit.setUpgradeId(newDisplayUUID); + }); + + upgradesManager.removeUpgradeDisplay(currentDisplayUUID); + Logger.debug("Added upgrade {} to beehive at {}", toAdd.id(), clickedBlock.getLocation()); + }); + } + + @EventHandler + public void onBreak(BlockBreakEvent event) { + UpgradesManager upgradesManager = this.plugin.getManager(UpgradesManager.class); + BeehiveManager beehiveManager = this.plugin.getManager(BeehiveManager.class); + Block block = event.getBlock(); + + beehiveManager.getBeehiveFromBlock(block.getState()).ifPresent(beehive -> { + UUID currentDisplayUUID = beehive.getUpgradeId(); + upgradesManager.removeUpgradeDisplay(currentDisplayUUID); + }); + } + + @EventHandler + public void onChunkLoad(ChunkLoadEvent event) { + UpgradesManager upgradesManager = this.plugin.getManager(UpgradesManager.class); + + Arrays.stream(event.getChunk().getTileEntities()).forEach(blockState -> { + if (!(blockState instanceof org.bukkit.block.Beehive beehiveState)) { + return; + } + upgradesManager.loadBeehive(beehiveState, Arrays.stream(event.getChunk().getEntities())); + }); + } + +} diff --git a/src/main/java/fr/traqueur/morebees/managers/BeeManagerImpl.java b/src/main/java/fr/traqueur/morebees/managers/BeeManagerImpl.java new file mode 100644 index 0000000..3db8bf3 --- /dev/null +++ b/src/main/java/fr/traqueur/morebees/managers/BeeManagerImpl.java @@ -0,0 +1,219 @@ +package fr.traqueur.morebees.managers; + +import com.destroystokyo.paper.entity.ai.Goal; +import com.destroystokyo.paper.entity.ai.VanillaGoal; +import fr.traqueur.morebees.api.managers.BeeManager; +import fr.traqueur.morebees.api.models.BeeType; +import fr.traqueur.morebees.api.models.Breed; +import fr.traqueur.morebees.api.models.Mutation; +import fr.traqueur.morebees.api.serialization.Keys; +import fr.traqueur.morebees.api.serialization.datas.BeeTypeDataType; +import fr.traqueur.morebees.api.settings.BreedSettings; +import fr.traqueur.morebees.api.settings.GlobalSettings; +import fr.traqueur.morebees.api.util.MiniMessageHelper; +import fr.traqueur.morebees.goals.BeePollinateGoal; +import fr.traqueur.morebees.goals.BeeTemptGoal; +import fr.traqueur.morebees.hooks.Hooks; +import fr.traqueur.morebees.hooks.modelengine.ModelEngineHook; +import fr.traqueur.morebees.listeners.BeeListener; +import fr.traqueur.recipes.api.RecipeType; +import fr.traqueur.recipes.impl.domains.ItemRecipe; +import fr.traqueur.recipes.impl.domains.recipes.RecipeBuilder; +import org.bukkit.*; +import org.bukkit.entity.*; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataContainer; +import org.jetbrains.annotations.NotNull; + +import javax.swing.text.html.Option; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class BeeManagerImpl implements BeeManager { + + public BeeManagerImpl() { + + this.getPlugin().registerListener(new BeeListener(this.getPlugin())); + + this.getPlugin().getSettings(GlobalSettings.class).bees().add(BeeType.NORMAL); + + Bukkit.getScheduler().runTask(this.getPlugin(), () -> { + + this.registerRecipes(); + + for (World world : Bukkit.getWorlds()) { + for (Bee bee : world.getEntitiesByClass(Bee.class)) { + this.getBeeTypeFromEntity(bee).ifPresent(beeType -> { + this.patchBee(bee, beeType); + }); + } + } + }); + } + + private void registerRecipes() { + for (BeeType bee : this.getPlugin().getSettings(GlobalSettings.class).bees()) { + if(bee == BeeType.NORMAL) { + continue; // Skip the normal bee type + } + String type = bee.type(); + ItemStack honey = bee.honey(1, false); + ItemStack honeyBlock = bee.honey(1, true); + + ItemRecipe honeyToBlock = new RecipeBuilder() + .setType(RecipeType.CRAFTING_SHAPED) + .setName(type + "_honeyblock") + .setResult(honeyBlock) + .setAmount(1) + .setPattern( + "HH ", + "HH ", + " " + ) + .addIngredient(honey, 'H', true) + .build(); + + ItemRecipe blockToHoney = new RecipeBuilder() + .setType(RecipeType.CRAFTING_SHAPELESS) + .setName(type + "_block_to_honey") + .setResult(honey) + .setAmount(4) + .addIngredient(honeyBlock, true) + .build(); + + ItemRecipe honeyToResource = new RecipeBuilder() + .setType(RecipeType.CRAFTING_SHAPELESS) + .setName(type + "_honey_to_resource") + .setResult(bee.productItem()) + .setAmount(1) + .addIngredient(honey, true) + .build(); + + this.getPlugin().getRecipesAPI().addRecipe(honeyToResource); + this.getPlugin().getRecipesAPI().addRecipe(blockToHoney); + this.getPlugin().getRecipesAPI().addRecipe(honeyToBlock); + + } + } + + @Override + public Optional getBeeTypeFromEgg(ItemStack itemStack) { + if (itemStack == null || itemStack.getType().isAir()) { + return Optional.empty(); + } + + if(itemStack.getType() != Material.BEE_SPAWN_EGG) { + return Optional.empty(); + } + + ItemMeta itemMeta = itemStack.getItemMeta(); + if (itemMeta == null) { + return Optional.empty(); + } + + PersistentDataContainer container = itemMeta.getPersistentDataContainer(); + return Optional.of(Keys.BEE_TYPE.get(container, BeeTypeDataType.INSTANCE, BeeType.NORMAL)); + } + + @Override + public Optional getBeeTypeFromEntity(LivingEntity entity) { + if (entity == null || entity.getType() != EntityType.BEE) { + return Optional.empty(); + } + PersistentDataContainer data = entity.getPersistentDataContainer(); + return Optional.of(Keys.BEE_TYPE.get(data, BeeTypeDataType.INSTANCE, BeeType.NORMAL)); + } + + @Override + public void spawnBee(Location location, @NotNull BeeType beeType, CreatureSpawnEvent.SpawnReason reason, boolean baby, boolean nectar) { + Bee bee = location.getWorld().createEntity(location, Bee.class); + + if(baby) + bee.setBaby(); + + bee.setHasNectar(nectar); + + this.patchBee(bee, beeType); + + bee.spawnAt(location, reason); + } + + @Override + public void patchBee(Bee bee, @NotNull BeeType beeType) { + + PersistentDataContainer data = bee.getPersistentDataContainer(); + Keys.BEE_TYPE.set(data, BeeTypeDataType.INSTANCE, beeType); + + if(!beeType.equals(BeeType.NORMAL)) { + Optional hookOptional = Hooks.MODEL_ENGINE.get(); + boolean textured = hookOptional.map(hook -> hook.overrideModel(bee, beeType)).orElse(false); + + if(!textured) { + bee.setCustomNameVisible(true); + bee.customName(MiniMessageHelper.parse(beeType.displayName())); + } + } + + Goal<@NotNull Creature> temptGoal = Bukkit.getMobGoals().getGoal(bee, VanillaGoal.TEMPT); + if (temptGoal != null) { + Bukkit.getMobGoals().removeGoal(bee, temptGoal); + } + Goal<@NotNull Bee> pollinateGoal = Bukkit.getMobGoals().getGoal(bee, VanillaGoal.BEE_POLLINATE); + if (pollinateGoal != null) { + Bukkit.getMobGoals().removeGoal(bee, pollinateGoal); + } + Bukkit.getMobGoals().addGoal(bee, 4, new BeePollinateGoal(this.getPlugin(), bee, beeType::isFlower)); + Bukkit.getMobGoals().addGoal(bee,3, new BeeTemptGoal(this.getPlugin(), bee, 1.25F, beeType::isFood)); + } + + @Override + public @NotNull BeeType computeChildType(@NotNull BeeType mother, @NotNull BeeType father) { + List parentsIds = new ArrayList<>(); + parentsIds.add(mother.type()); + parentsIds.add(father.type()); + parentsIds.sort(String::compareTo); + + Breed breed = this.getPlugin().getSettings(BreedSettings.class).breeds() + .stream() + .filter(breedChecked -> { + List configParents = new ArrayList<>(breedChecked.parents()); + if (configParents.size() != 2) return false; + configParents.sort(String::compareTo); + return configParents.equals(parentsIds); + }) + .findFirst() + .orElse(null); + + if (breed != null && Math.random() < breed.chance()) { + return this.getPlugin().getSettings(GlobalSettings.class).getBeeType(breed.child()).orElseThrow(); + } + + return Math.random() < 0.5 ? mother : father; + } + + @Override + public void feed(@NotNull Player player, Bee bee) { + if(bee.canBreed()) { + bee.getWorld().spawnParticle(Particle.HEART, + bee.getLocation().add(0, 0.1, 0), + 7, 0.5, 0.5, 0.5, 0); + bee.setLoveModeTicks(600); + bee.setBreedCause(player.getUniqueId()); + } else if (!bee.isAdult()) { + bee.setAge(bee.getAge() - ((int) (bee.getAge() * 0.1))); + } + } + + @Override + public void mutate(Bee bee, Mutation mutation, Location to) { + bee.setHasNectar(false); + to.getBlock().setType(Material.AIR); + this.getPlugin().getSettings(GlobalSettings.class).getBeeType(mutation.child()).ifPresent(beeType -> { + to.getWorld().dropItem(to, beeType.egg()); + }); + } + +} diff --git a/src/main/java/fr/traqueur/morebees/managers/BeehiveManagerImpl.java b/src/main/java/fr/traqueur/morebees/managers/BeehiveManagerImpl.java new file mode 100644 index 0000000..c4e47c4 --- /dev/null +++ b/src/main/java/fr/traqueur/morebees/managers/BeehiveManagerImpl.java @@ -0,0 +1,63 @@ +package fr.traqueur.morebees.managers; + +import fr.traqueur.morebees.api.managers.BeehiveManager; +import fr.traqueur.morebees.api.models.Beehive; +import fr.traqueur.morebees.api.serialization.Keys; +import fr.traqueur.morebees.api.serialization.datas.BeehiveDataType; +import fr.traqueur.morebees.listeners.BeehiveListener; +import fr.traqueur.morebees.models.BeehiveImpl; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.inventory.ItemStack; +import org.bukkit.persistence.PersistentDataContainer; + +import java.util.Optional; +import java.util.function.Consumer; + +public class BeehiveManagerImpl implements BeehiveManager { + + public BeehiveManagerImpl() { + this.getPlugin().registerListener(new BeehiveListener(this.getPlugin())); + } + + @Override + public Optional getBeehiveFromBlock(BlockState block) { + if(!(block instanceof org.bukkit.block.Beehive beehive)) { + return Optional.empty(); + } + PersistentDataContainer container = beehive.getPersistentDataContainer(); + return Optional.of(Keys.BEEHIVE.get(container, BeehiveDataType.INSTANCE, new BeehiveImpl())); + } + + @Override + public Optional getBeehiveFromItem(ItemStack stack) { + if (stack == null || stack.getType().isAir()) { + return Optional.empty(); + } + PersistentDataContainer container = stack.getItemMeta() != null ? stack.getItemMeta().getPersistentDataContainer() : null; + if (container == null) { + return Optional.empty(); + } + return Keys.BEEHIVE.get(container, BeehiveDataType.INSTANCE); + } + + @Override + public void saveBeehiveToBlock(Block block, Beehive beehive) { + if(!(block.getState() instanceof org.bukkit.block.Beehive beehiveState)) { + return; + } + PersistentDataContainer container = beehiveState.getPersistentDataContainer(); + Keys.BEEHIVE.set(container, BeehiveDataType.INSTANCE, beehive); + beehiveState.setMaxEntities(beehive.getUpgrade().maxBees()); + beehiveState.update(); + } + + @Override + public void editBeehive(Block block, Consumer consumer) { + this.getBeehiveFromBlock(block.getState()).ifPresent(beehive -> { + consumer.accept(beehive); + this.saveBeehiveToBlock(block, beehive); + }); + } + +} diff --git a/src/main/java/fr/traqueur/morebees/managers/ToolsManagerImpl.java b/src/main/java/fr/traqueur/morebees/managers/ToolsManagerImpl.java new file mode 100644 index 0000000..62d0d8c --- /dev/null +++ b/src/main/java/fr/traqueur/morebees/managers/ToolsManagerImpl.java @@ -0,0 +1,118 @@ +package fr.traqueur.morebees.managers; + +import fr.traqueur.morebees.api.managers.ToolsManager; +import fr.traqueur.morebees.api.models.BeeData; +import fr.traqueur.morebees.api.models.BeeType; +import fr.traqueur.morebees.api.models.Tool; +import fr.traqueur.morebees.api.serialization.Keys; +import fr.traqueur.morebees.api.serialization.datas.BeeDataDataType; +import fr.traqueur.morebees.api.serialization.datas.ToolDataType; +import fr.traqueur.morebees.listeners.ToolsListener; +import fr.traqueur.morebees.models.BeeDataImpl; +import net.kyori.adventure.text.Component; +import org.bukkit.entity.Bee; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class ToolsManagerImpl implements ToolsManager { + + public ToolsManagerImpl() { + this.getPlugin().registerListener(new ToolsListener(this.getPlugin())); + } + + @Override + public Optional getTool(ItemStack itemStack) { + if (itemStack == null) { + return Optional.empty(); + } + ItemMeta itemMeta = itemStack.getItemMeta(); + if (itemMeta == null) { + return Optional.empty(); + } + PersistentDataContainer dataContainer = itemMeta.getPersistentDataContainer(); + return Keys.TOOL_ID.get(dataContainer, ToolDataType.INSTANCE); + } + + @Override + public boolean isFull(ItemStack itemStack) { + return this.getTool(itemStack).map(tool -> { + PersistentDataContainer dataContainer = itemStack.getItemMeta().getPersistentDataContainer(); + int maxBees = tool.maxBees(); + List bees = Keys.BEES.get(dataContainer, PersistentDataType.LIST.listTypeFrom(BeeDataDataType.INSTANCE), new ArrayList<>()); + return bees.size() >= maxBees; + }).orElse(true); // Default to true if no tool is found + } + + @Override + public BeeData toData(Bee bee, BeeType beeType) { + return new BeeDataImpl(beeType, bee.hasNectar(), bee.isAdult()); + } + + @Override + public void catchBee(ItemStack tool, Bee bee, BeeType beeType) { + if (tool == null || this.getTool(tool).isEmpty()) { + return; + } + Tool toolType = this.getTool(tool).orElseThrow(() -> new IllegalArgumentException("ItemStack is not a valid tool")); + List bees = this.getBeesInsideTool(tool); + + if (bees.size() >= toolType.maxBees()) { + return; // Tool is full + } + + bee.remove(); + bees.add(this.toData(bee, beeType)); + + List newLore = toolType.lore(bees); + tool.editMeta(meta -> { + PersistentDataContainer dataContainer = meta.getPersistentDataContainer(); + Keys.BEES.set(dataContainer, PersistentDataType.LIST.listTypeFrom(BeeDataDataType.INSTANCE), bees); + meta.lore(newLore); + }); + } + + @Override + public List releaseBee(ItemStack tool, boolean all) { + if (tool == null || this.getTool(tool).isEmpty()) { + return List.of(); + } + Tool toolType = this.getTool(tool).orElseThrow(() -> new IllegalArgumentException("ItemStack is not a valid tool")); + List bees = this.getBeesInsideTool(tool); + + if (bees.isEmpty()) { + return List.of(); + } + + int nbBees = all ? bees.size() : 1; + + List releasedBees = new ArrayList<>(); + for (int i = 0; i < nbBees && !bees.isEmpty(); i++) { + releasedBees.add(bees.removeLast()); + } + + List newLore = toolType.lore(bees); + tool.editMeta(meta -> { + PersistentDataContainer dataContainer = meta.getPersistentDataContainer(); + Keys.BEES.set(dataContainer, PersistentDataType.LIST.listTypeFrom(BeeDataDataType.INSTANCE), bees); + meta.lore(newLore); + }); + return releasedBees; + } + + private List getBeesInsideTool(ItemStack tool) { + if (tool == null || this.getTool(tool).isEmpty()) { + return List.of(); + } + PersistentDataContainer dataContainer = tool.getItemMeta().getPersistentDataContainer(); + List bees = Keys.BEES.get(dataContainer, PersistentDataType.LIST.listTypeFrom(BeeDataDataType.INSTANCE), new ArrayList<>()); + return new ArrayList<>(bees); + } + + +} diff --git a/src/main/java/fr/traqueur/morebees/managers/UpgradesManagerImpl.java b/src/main/java/fr/traqueur/morebees/managers/UpgradesManagerImpl.java new file mode 100644 index 0000000..0b02c9d --- /dev/null +++ b/src/main/java/fr/traqueur/morebees/managers/UpgradesManagerImpl.java @@ -0,0 +1,149 @@ +package fr.traqueur.morebees.managers; + +import fr.traqueur.morebees.api.managers.BeehiveManager; +import fr.traqueur.morebees.api.managers.UpgradesManager; +import fr.traqueur.morebees.api.models.Upgrade; +import fr.traqueur.morebees.api.serialization.Keys; +import fr.traqueur.morebees.api.serialization.datas.UpgradeDataType; +import fr.traqueur.morebees.listeners.UpgradesListener; +import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.block.data.type.Beehive; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.ItemDisplay; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.util.Transformation; +import org.joml.AxisAngle4f; +import org.joml.Vector3f; + +import java.util.Arrays; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Stream; + +public class UpgradesManagerImpl implements UpgradesManager { + + private static final float DISPLAY_SIZE = 0.3f; // Taille de l'item display (petit) + private static final float OFFSET_FROM_FACE = 0.02f; // Distance de la face + private static final float CORNER_OFFSET = 0.15f; + + public UpgradesManagerImpl() { + this.getPlugin().registerListener(new UpgradesListener(this.getPlugin())); + + Bukkit.getScheduler().runTask(this.getPlugin(),() -> { + for (World world : Bukkit.getWorlds()) { + for (Chunk loadedChunk : world.getLoadedChunks()) { + for (BlockState tileEntity : loadedChunk.getTileEntities()) { + if (tileEntity instanceof org.bukkit.block.Beehive beehiveState) { + this.loadBeehive(beehiveState, Arrays.stream(loadedChunk.getEntities())); + } + } + } + } + }); + } + + @Override + public Optional getUpgradeFromItem(ItemStack itemStack) { + if (itemStack == null || itemStack.getType().isAir()) { + return Optional.empty(); + } + ItemMeta itemMeta = itemStack.getItemMeta(); + if (itemMeta == null) { + return Optional.empty(); + } + PersistentDataContainer persistentDataContainer = itemMeta.getPersistentDataContainer(); + return Keys.UPGRADE_ID.get(persistentDataContainer, UpgradeDataType.INSTANCE); + } + + @Override + public ItemDisplay createUpgradeDisplay(Block beehiveBlock, Upgrade upgrade) { + if (upgrade.equals(Upgrade.NONE)) { + return null; + } + + Location displayLocation = calculateDisplayLocation(beehiveBlock); + if (displayLocation == null) { + return null; + } + + ItemDisplay itemDisplay = displayLocation.getWorld().spawn(displayLocation, ItemDisplay.class); + + // Configuration de l'ItemDisplay + ItemStack upgradeItem = upgrade.build(); + itemDisplay.setItemStack(upgradeItem); + + // Transformation pour la taille et la position + Transformation transformation = new Transformation( + new Vector3f(0, 0, 0), // Translation + new AxisAngle4f(0, 0, 0, 1), // Rotation gauche + new Vector3f(DISPLAY_SIZE, DISPLAY_SIZE, DISPLAY_SIZE), // Scale + new AxisAngle4f(0, 0, 0, 1) // Rotation droite + ); + itemDisplay.setTransformation(transformation); + + // Configuration additionnelle + itemDisplay.setBillboard(ItemDisplay.Billboard.FIXED); + itemDisplay.setViewRange(32.0f); + itemDisplay.setPersistent(true); + itemDisplay.setGlowing(false); + + return itemDisplay; + } + + @Override + public void removeUpgradeDisplay(UUID displayUUID) { + if (displayUUID == null) return; + + Bukkit.getWorlds().forEach(world -> { + world.getEntities().stream() + .filter(entity -> entity instanceof ItemDisplay) + .filter(entity -> entity.getUniqueId().equals(displayUUID)) + .forEach(org.bukkit.entity.Entity::remove); + }); + } + + @Override + public void loadBeehive(org.bukkit.block.Beehive beehiveState, Stream entities) { + BeehiveManager beehiveManager = this.getPlugin().getManager(BeehiveManager.class); + beehiveManager.getBeehiveFromBlock(beehiveState).ifPresent(beehive -> { + Upgrade upgrade = beehive.getUpgrade(); + UUID currentDisplayUUID = beehive.getUpgradeId(); + if ((!upgrade.equals(Upgrade.NONE) && currentDisplayUUID == null) || + entities.noneMatch(entity -> entity.getUniqueId().equals(currentDisplayUUID) && entity.getType() == EntityType.ITEM_DISPLAY)) { + ItemDisplay newDisplay = this.createUpgradeDisplay(beehiveState.getBlock(), upgrade); + UUID newDisplayUUID = newDisplay == null ? null : newDisplay.getUniqueId(); + beehiveManager.editBeehive(beehiveState.getBlock(), beehiveToEdit -> { + beehiveToEdit.setUpgradeId(newDisplayUUID); + }); + } + }); + } + + private Location calculateDisplayLocation(Block beehiveBlock) { + if (!(beehiveBlock.getBlockData() instanceof Beehive beehiveData)) { + return null; + } + + Location blockLocation = beehiveBlock.getLocation().add(0.5, 0.5, 0.5); + + // Calculer la position selon la direction de la ruche + return switch (beehiveData.getFacing()) { + case NORTH -> blockLocation.add(-CORNER_OFFSET, CORNER_OFFSET, -0.5 - OFFSET_FROM_FACE); + case SOUTH -> blockLocation.add(CORNER_OFFSET, CORNER_OFFSET, 0.5 + OFFSET_FROM_FACE); + case EAST -> blockLocation.add(0.5 + OFFSET_FROM_FACE, CORNER_OFFSET, -CORNER_OFFSET); + case WEST -> blockLocation.add(-0.5 - OFFSET_FROM_FACE, CORNER_OFFSET, CORNER_OFFSET); + default -> null; + }; + } + + + +} diff --git a/src/main/java/fr/traqueur/morebees/models/BeeDataImpl.java b/src/main/java/fr/traqueur/morebees/models/BeeDataImpl.java new file mode 100644 index 0000000..f53431b --- /dev/null +++ b/src/main/java/fr/traqueur/morebees/models/BeeDataImpl.java @@ -0,0 +1,7 @@ +package fr.traqueur.morebees.models; + +import fr.traqueur.morebees.api.models.BeeData; +import fr.traqueur.morebees.api.models.BeeType; + +public record BeeDataImpl(BeeType type, boolean hasNectar, boolean isAdult) implements BeeData { +} diff --git a/src/main/java/fr/traqueur/morebees/models/BeehiveImpl.java b/src/main/java/fr/traqueur/morebees/models/BeehiveImpl.java new file mode 100644 index 0000000..74d444f --- /dev/null +++ b/src/main/java/fr/traqueur/morebees/models/BeehiveImpl.java @@ -0,0 +1,99 @@ +package fr.traqueur.morebees.models; + +import fr.traqueur.morebees.api.BeePlugin; +import fr.traqueur.morebees.api.models.BeeType; +import fr.traqueur.morebees.api.models.Beehive; +import fr.traqueur.morebees.api.models.Upgrade; +import fr.traqueur.morebees.api.serialization.Keys; +import fr.traqueur.morebees.api.serialization.datas.BeehiveDataType; +import fr.traqueur.morebees.api.settings.GlobalSettings; +import fr.traqueur.morebees.api.util.Util; +import org.bukkit.inventory.ItemStack; +import org.bukkit.persistence.PersistentDataContainer; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +public class BeehiveImpl implements Beehive { + + private final Map honeyCombCounts; + private @NotNull Upgrade upgrade; + private UUID upgradeId; + + public BeehiveImpl() { + this.honeyCombCounts = new HashMap<>(); + this.upgrade = Upgrade.NONE; + this.upgradeId = null; + } + + public BeehiveImpl(Map honeyCombCounts, @NotNull Upgrade upgrade) { + this.honeyCombCounts = honeyCombCounts; + this.upgrade = upgrade; + this.upgradeId = null; + } + + @Override + public @NotNull Upgrade getUpgrade() { + return upgrade; + } + + @Override + public void setUpgrade(@NotNull Upgrade upgrade) { + this.upgrade = upgrade; + } + + @Override + public UUID getUpgradeId() { + return upgradeId; + } + + @Override + public void setUpgradeId(UUID upgradeId) { + this.upgradeId = upgradeId; + } + + @Override + public Map getHoneyCombCounts() { + return honeyCombCounts; + } + + @Override + public int getHoneyCombCount(@NotNull BeeType beeType) { + return honeyCombCounts.getOrDefault(beeType, 0); + } + + @Override + public void addHoney(@NotNull BeeType beeType, int i) { + honeyCombCounts.merge(beeType, 1, Integer::sum); + } + + @Override + public void removeHoney(@NotNull BeeType beeType, int i) { + honeyCombCounts.merge(beeType, -i, Integer::sum); + if (honeyCombCounts.get(beeType) <= 0) { + honeyCombCounts.remove(beeType); + } + } + + @Override + public @NotNull ItemStack patch(@NotNull ItemStack item) { + if (item.getType().isAir()) { + return item; + } + + ItemStack patchedItem = item.clone(); + patchedItem.editMeta(itemMeta -> { + PersistentDataContainer container = itemMeta.getPersistentDataContainer(); + Keys.BEEHIVE.set(container, BeehiveDataType.INSTANCE, this); + List additionalLore = BeePlugin.getPlugin(BeePlugin.class).getSettings(GlobalSettings.class).beehiveLore(); + if(!this.upgrade.equals(Upgrade.NONE) && additionalLore != null) { + itemMeta.lore(Util.parseLore(additionalLore, this.upgrade.formatters())); + } + }); + return patchedItem; + } + +} diff --git a/src/main/java/fr/traqueur/morebees/recipes/MoreBeesHook.java b/src/main/java/fr/traqueur/morebees/recipes/MoreBeesHook.java new file mode 100644 index 0000000..89f07ab --- /dev/null +++ b/src/main/java/fr/traqueur/morebees/recipes/MoreBeesHook.java @@ -0,0 +1,40 @@ +package fr.traqueur.morebees.recipes; + +import fr.traqueur.morebees.api.BeePlugin; +import fr.traqueur.morebees.api.models.Tool; +import fr.traqueur.morebees.api.models.Upgrade; +import fr.traqueur.morebees.api.settings.UpgradeSettings; +import fr.traqueur.recipes.api.domains.Ingredient; +import fr.traqueur.recipes.api.hook.Hook; +import org.bukkit.inventory.ItemStack; + +import java.util.List; + +public class MoreBeesHook implements Hook { + + private final BeePlugin plugin; + + public MoreBeesHook(BeePlugin plugin) { + this.plugin = plugin; + } + + @Override + public String getPluginName() { + return this.plugin.getName(); + } + + @Override + public Ingredient getIngredient(String data, Character sign) { + return new ToolsIngredient(this.plugin, data, sign); + } + + @Override + public ItemStack getItemStack(String resultPart) { + try { + Tool tool = Tool.valueOf(resultPart.toUpperCase()); + return tool.itemStack(List.of()); + } catch (Exception e) { + return this.plugin.getSettings(UpgradeSettings.class).getUpgrade(resultPart).map(Upgrade::build).orElseThrow(); + } + } +} diff --git a/src/main/java/fr/traqueur/morebees/recipes/ToolsIngredient.java b/src/main/java/fr/traqueur/morebees/recipes/ToolsIngredient.java new file mode 100644 index 0000000..9f0f787 --- /dev/null +++ b/src/main/java/fr/traqueur/morebees/recipes/ToolsIngredient.java @@ -0,0 +1,61 @@ +package fr.traqueur.morebees.recipes; + +import fr.traqueur.morebees.api.BeePlugin; +import fr.traqueur.morebees.api.models.Tool; +import fr.traqueur.morebees.api.models.Upgrade; +import fr.traqueur.morebees.api.serialization.Keys; +import fr.traqueur.morebees.api.serialization.datas.ToolDataType; +import fr.traqueur.morebees.api.serialization.datas.UpgradeDataType; +import fr.traqueur.morebees.api.settings.UpgradeSettings; +import fr.traqueur.recipes.api.domains.Ingredient; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.RecipeChoice; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataContainer; + +import java.util.List; + +public class ToolsIngredient extends Ingredient { + + private final BeePlugin plugin; + private final String data; + + public ToolsIngredient(BeePlugin plugin, String data, Character sign) { + super(sign); + this.plugin = plugin; + this.data = data; + } + + @Override + public boolean isSimilar(ItemStack item) { + ItemMeta meta = item.getItemMeta(); + if (meta == null) return false; + PersistentDataContainer container = meta.getPersistentDataContainer(); + try { + Tool tool = Tool.valueOf(this.data.toUpperCase()); + return Keys.TOOL_ID.get(container, ToolDataType.INSTANCE).map(toolFound -> toolFound == tool).orElse(false); + } catch (IllegalArgumentException e) { + Upgrade toCheck = this.plugin.getSettings(UpgradeSettings.class).getUpgrade(this.data).orElse(null); + if (toCheck == null) return false; + return Keys.UPGRADE_ID.get(container, UpgradeDataType.INSTANCE).map(upgradeFound -> upgradeFound.id().equalsIgnoreCase(toCheck.id())).orElse(false); + } + } + + @Override + public RecipeChoice choice() { + Material material; + try { + Tool tool = Tool.valueOf(this.data.toUpperCase()); + material = tool.itemStack(List.of()).getType(); + } catch (IllegalArgumentException e) { + Upgrade upgrade = this.plugin.getSettings(UpgradeSettings.class).getUpgrade(this.data).orElse(null); + if (upgrade == null) { + throw new IllegalArgumentException("Invalid tool or upgrade: " + this.data, e); + } + material = upgrade.build().getType(); + } + return new RecipeChoice.MaterialChoice(material); + } + +} \ No newline at end of file diff --git a/src/main/java/fr/traqueur/morebees/serialization/BeeDataDataTypeImpl.java b/src/main/java/fr/traqueur/morebees/serialization/BeeDataDataTypeImpl.java new file mode 100644 index 0000000..9a50c6e --- /dev/null +++ b/src/main/java/fr/traqueur/morebees/serialization/BeeDataDataTypeImpl.java @@ -0,0 +1,37 @@ +package fr.traqueur.morebees.serialization; + +import fr.traqueur.morebees.api.models.BeeData; +import fr.traqueur.morebees.api.models.BeeType; +import fr.traqueur.morebees.api.serialization.Keys; +import fr.traqueur.morebees.api.serialization.datas.BeeDataDataType; +import fr.traqueur.morebees.api.serialization.datas.BeeTypeDataType; +import fr.traqueur.morebees.models.BeeDataImpl; +import org.bukkit.persistence.PersistentDataAdapterContext; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; +import org.jetbrains.annotations.NotNull; + +public class BeeDataDataTypeImpl extends BeeDataDataType { + + public static void init() { + BeeDataDataType.INSTANCE = new BeeDataDataTypeImpl(); + } + + @Override + public @NotNull PersistentDataContainer toPrimitive(@NotNull BeeData complex, @NotNull PersistentDataAdapterContext context) { + PersistentDataContainer container = context.newPersistentDataContainer(); + Keys.INTERNAL_BEE_DATA_BEE_TYPE.set(container, BeeTypeDataType.INSTANCE, complex.type()); + Keys.INTERNAL_BEE_DATA_HAS_NECTAR.set(container, PersistentDataType.BOOLEAN, complex.hasNectar()); + Keys.INTERNAL_BEE_DATA_IS_ADULT.set(container, PersistentDataType.BOOLEAN, complex.isAdult()); + return container; + } + + @Override + public @NotNull BeeData fromPrimitive(@NotNull PersistentDataContainer primitive, @NotNull PersistentDataAdapterContext context) { + BeeType beeType = Keys.INTERNAL_BEE_DATA_BEE_TYPE.get(primitive, BeeTypeDataType.INSTANCE).orElseThrow(); + boolean hasNectar = Keys.INTERNAL_BEE_DATA_HAS_NECTAR.get(primitive, PersistentDataType.BOOLEAN).orElseThrow(); + boolean isAdult = Keys.INTERNAL_BEE_DATA_IS_ADULT.get(primitive, PersistentDataType.BOOLEAN).orElseThrow(); + + return new BeeDataImpl(beeType, hasNectar, isAdult); + } +} diff --git a/src/main/java/fr/traqueur/morebees/serialization/BeeTypeDataTypeImpl.java b/src/main/java/fr/traqueur/morebees/serialization/BeeTypeDataTypeImpl.java new file mode 100644 index 0000000..ffd0904 --- /dev/null +++ b/src/main/java/fr/traqueur/morebees/serialization/BeeTypeDataTypeImpl.java @@ -0,0 +1,31 @@ +package fr.traqueur.morebees.serialization; + +import fr.traqueur.morebees.api.BeePlugin; +import fr.traqueur.morebees.api.models.BeeType; +import fr.traqueur.morebees.api.serialization.datas.BeeTypeDataType; +import fr.traqueur.morebees.api.settings.GlobalSettings; +import org.bukkit.persistence.PersistentDataAdapterContext; +import org.jetbrains.annotations.NotNull; + +public class BeeTypeDataTypeImpl extends BeeTypeDataType { + + public static void init(@NotNull BeePlugin plugin) { + BeeTypeDataType.INSTANCE = new BeeTypeDataTypeImpl(plugin); + } + + private final BeePlugin plugin; + + private BeeTypeDataTypeImpl(@NotNull BeePlugin plugin) { + this.plugin = plugin; + } + + @Override + public @NotNull String toPrimitive(@NotNull BeeType complex, @NotNull PersistentDataAdapterContext context) { + return complex.type(); + } + + @Override + public @NotNull BeeType fromPrimitive(@NotNull String primitive, @NotNull PersistentDataAdapterContext context) { + return plugin.getSettings(GlobalSettings.class).bees().stream().filter(beeType -> beeType.type().equals(primitive)).findFirst().orElseThrow(); + } +} diff --git a/src/main/java/fr/traqueur/morebees/serialization/BeehiveDataTypeImpl.java b/src/main/java/fr/traqueur/morebees/serialization/BeehiveDataTypeImpl.java new file mode 100644 index 0000000..49d264d --- /dev/null +++ b/src/main/java/fr/traqueur/morebees/serialization/BeehiveDataTypeImpl.java @@ -0,0 +1,53 @@ +package fr.traqueur.morebees.serialization; + +import fr.traqueur.morebees.api.models.BeeType; +import fr.traqueur.morebees.api.models.Beehive; +import fr.traqueur.morebees.api.models.Upgrade; +import fr.traqueur.morebees.api.serialization.Keys; +import fr.traqueur.morebees.api.serialization.MapDataType; +import fr.traqueur.morebees.api.serialization.datas.BeeTypeDataType; +import fr.traqueur.morebees.api.serialization.datas.BeehiveDataType; +import fr.traqueur.morebees.api.serialization.datas.UpgradeDataType; +import fr.traqueur.morebees.models.BeehiveImpl; +import org.bukkit.persistence.PersistentDataAdapterContext; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class BeehiveDataTypeImpl extends BeehiveDataType { + + private static final MapDataType TYPE = new MapDataType<>(HashMap::new,BeeTypeDataType.INSTANCE, PersistentDataType.INTEGER); + + public static void init() { + BeehiveDataType.INSTANCE = new BeehiveDataTypeImpl(); + } + @Override + public @NotNull PersistentDataContainer toPrimitive(@NotNull Beehive complex, @NotNull PersistentDataAdapterContext context) { + PersistentDataContainer container = context.newPersistentDataContainer(); + Keys.INTERNAL_BEEHIVE_BEE_TYPES.set(container, TYPE, complex.getHoneyCombCounts()); + Keys.INTERNAL_BEEHIVE_UPGRADE.set(container, UpgradeDataType.INSTANCE, complex.getUpgrade()); + if( complex.getUpgradeId() != null) { + Keys.INTERNAL_BEEHIVE_DISPLAY_ID.set(container, PersistentDataType.STRING, complex.getUpgradeId().toString()); + } + + return container; + } + + @Override + public @NotNull Beehive fromPrimitive(@NotNull PersistentDataContainer primitive, @NotNull PersistentDataAdapterContext context) { + Map honeyCombCounts = Keys.INTERNAL_BEEHIVE_BEE_TYPES.get(primitive, TYPE, new HashMap<>()); + Upgrade upgrade = Keys.INTERNAL_BEEHIVE_UPGRADE.get(primitive, UpgradeDataType.INSTANCE, Upgrade.NONE); + + Beehive beehive = new BeehiveImpl(honeyCombCounts, upgrade); + + Keys.INTERNAL_BEEHIVE_DISPLAY_ID.get(primitive, PersistentDataType.STRING).ifPresent(displayId -> { + UUID upgradeId = UUID.fromString(displayId); + beehive.setUpgradeId(upgradeId); + }); + return beehive; + } +} diff --git a/src/main/java/fr/traqueur/morebees/serialization/ToolDataTypeImpl.java b/src/main/java/fr/traqueur/morebees/serialization/ToolDataTypeImpl.java new file mode 100644 index 0000000..0c29c32 --- /dev/null +++ b/src/main/java/fr/traqueur/morebees/serialization/ToolDataTypeImpl.java @@ -0,0 +1,23 @@ +package fr.traqueur.morebees.serialization; + +import fr.traqueur.morebees.api.models.Tool; +import fr.traqueur.morebees.api.serialization.datas.ToolDataType; +import org.bukkit.persistence.PersistentDataAdapterContext; +import org.jetbrains.annotations.NotNull; + +public class ToolDataTypeImpl extends ToolDataType { + + public static void init() { + ToolDataType.INSTANCE = new ToolDataTypeImpl(); + } + + @Override + public @NotNull String toPrimitive(@NotNull Tool complex, @NotNull PersistentDataAdapterContext context) { + return complex.name(); + } + + @Override + public @NotNull Tool fromPrimitive(@NotNull String primitive, @NotNull PersistentDataAdapterContext context) { + return Tool.valueOf(primitive); + } +} diff --git a/src/main/java/fr/traqueur/morebees/serialization/UpgradeDataTypeImpl.java b/src/main/java/fr/traqueur/morebees/serialization/UpgradeDataTypeImpl.java new file mode 100644 index 0000000..9e83e02 --- /dev/null +++ b/src/main/java/fr/traqueur/morebees/serialization/UpgradeDataTypeImpl.java @@ -0,0 +1,31 @@ +package fr.traqueur.morebees.serialization; + +import fr.traqueur.morebees.api.BeePlugin; +import fr.traqueur.morebees.api.models.Upgrade; +import fr.traqueur.morebees.api.serialization.datas.UpgradeDataType; +import fr.traqueur.morebees.api.settings.UpgradeSettings; +import org.bukkit.persistence.PersistentDataAdapterContext; +import org.jetbrains.annotations.NotNull; + +public class UpgradeDataTypeImpl extends UpgradeDataType { + + public static void init(BeePlugin plugin) { + UpgradeDataType.INSTANCE = new UpgradeDataTypeImpl(plugin); + } + + private final BeePlugin plugin; + + private UpgradeDataTypeImpl(BeePlugin plugin) { + this.plugin = plugin; + } + + @Override + public @NotNull String toPrimitive(@NotNull Upgrade complex, @NotNull PersistentDataAdapterContext context) { + return complex.id(); + } + + @Override + public @NotNull Upgrade fromPrimitive(@NotNull String primitive, @NotNull PersistentDataAdapterContext context) { + return this.plugin.getSettings(UpgradeSettings.class).getUpgrade(primitive).orElse(Upgrade.NONE); + } +} diff --git a/src/main/resources/breeds.yml b/src/main/resources/breeds.yml new file mode 100644 index 0000000..1a83940 --- /dev/null +++ b/src/main/resources/breeds.yml @@ -0,0 +1,11 @@ +breeds: +- parents: + - redstone-bee + - diamond-bee + child: emerald-bee + chance: 1.0 +mutations: +- parent: redstone-bee + child: emerald-bee + blocks: + - REDSTONE_ORE diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..b00fb96 --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1,65 @@ +debug: true +# Define the name of the flying animation for all bee model +fly-animation: flying +# Field "model" permits to use a custom model from model engine remove it to not use a custom model +bees: +- type: redstone-bee + model-id: 1 + display-name: Redstone Bee + foods: + - REDSTONE + flowers: + - REDSTONE_BLOCK + product: REDSTONE_ORE + model: redstone-bee +- type: emerald-bee + display-name: Emerald Bee + foods: + - EMERALD + flowers: + - EMERALD_BLOCK + product: EMERALD_ORE +- type: diamond-bee + display-name: Diamond Bee + foods: + - DIAMOND + flowers: + - DIAMOND_BLOCK + product: DIAMOND_ORE +- type: gold-bee + display-name: Gold Bee + foods: + - GOLD_INGOT + flowers: + - GOLD_BLOCK + product: GOLD_ORE +- type: iron-bee + display-name: Iron Bee + foods: + - IRON_INGOT + flowers: + - IRON_BLOCK + product: IRON_ORE +# This key represent the beebox tool to get bees inside +bee-box: + material: PAPER + name: Bee box + lore: + - This is a beebox + - '%bees%' +bee-box-size: 10 +# This key represent the bee jar tool to get one bee inside +bee-jar: + material: GLASS_BOTTLE + name: Bee jar + lore: + - This is a bee jar + - '%bee%' +# This key represent the additonal lore for beehive patch +beehive-lore: +- This is a beehive patch +- You can use it to patch your beehive +- It will add a new lore to your beehive +- 'production multiplier: %production-multiplier%' +- 'produce-blocks: %produce-blocks%' +- 'max-bees: %max-bees%' diff --git a/src/main/resources/messages.yml b/src/main/resources/messages.yml new file mode 100644 index 0000000..757c593 --- /dev/null +++ b/src/main/resources/messages.yml @@ -0,0 +1,32 @@ +command-help-title: "%plugin% %version% by %authors% - Commands List" +command-syntax: "%usage% %description%" + +reload-command-desc: "Reloads the plugin configuration." +reload-success: "Configuration reloaded successfully!" + +command-amount-invalid: "Invalid amount for %amount%! Please choose a number between 1 and %max-amount%." + +egg-command-desc: "Gives you a bee egg." +egg-command-success: "Successfully given %amount% %beetype% egg(s) to %player%!" + +spawn-command-desc: "Spawn a bee at the player location." +spawn-command-success: "Successfully spawned a %beetype% bee at your location!" + +honey-command-desc: "Gives you honey from a bee type." +honey-command-success: "Successfully given %amount% honey from %beetype% to %player%!" + +empty-bee-jar: "Empty" +empty-bee-box: "Empty" +bee-jar-content: "%beetype%" +bee-box-content: "%beetype% x%amount%" + +tool-full: "Your %tool% is already full!" + +tool-command-desc: "Gives a tool to a player." +tool-command-success: "Successfully given %tool% to %player%!" + +produce-blocks-no: "No" +produce-blocks-yes: "Yes" + +upgrade-command-desc: "Gives an upgrade to a player." +upgrade-command-success: "Successfully given %upgrade% to %player%!" \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 842f051..8658d0b 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -3,4 +3,9 @@ version: '${version}' main: fr.traqueur.morebees.MoreBees api-version: '1.21' authors: [Traqueur_] +soft-depend: + - ItemsAdder + - ModelEngine + - Oraxen + - Nexo libraries: ${libraries} diff --git a/src/main/resources/recipes/beebox.yml b/src/main/resources/recipes/beebox.yml new file mode 100644 index 0000000..b81074e --- /dev/null +++ b/src/main/resources/recipes/beebox.yml @@ -0,0 +1,20 @@ +type: CRAFTING_SHAPED + +group: beetools + +category: MISC + +pattern: + - "###" + - "#H#" + - "###" + +result: + item: "morebees:BEE_BOX" + amount: 1 + +ingredients: + - item: "tag:PLANKS" + sign: '#' + - item: "material:HONEYCOMB" + sign: 'H' \ No newline at end of file diff --git a/src/main/resources/recipes/beejar.yml b/src/main/resources/recipes/beejar.yml new file mode 100644 index 0000000..ae75cdb --- /dev/null +++ b/src/main/resources/recipes/beejar.yml @@ -0,0 +1,18 @@ +type: CRAFTING_SHAPED + +group: beetools + +category: MISC + +pattern: + - " # " + - "# #" + - "###" + +result: + item: "morebees:BEE_JAR" + amount: 1 + +ingredients: + - item: "material:GLASS_PANE" + sign: '#' \ No newline at end of file diff --git a/src/main/resources/recipes/upgrade-1.yml b/src/main/resources/recipes/upgrade-1.yml new file mode 100644 index 0000000..9ad2f59 --- /dev/null +++ b/src/main/resources/recipes/upgrade-1.yml @@ -0,0 +1,20 @@ +type: CRAFTING_SHAPED + +group: beeupgrade + +category: MISC + +pattern: + - "###" + - "#H#" + - "###" + +result: + item: "morebees:level-1" + amount: 1 + +ingredients: + - item: "material:SHORT_GRASS" + sign: '#' + - item: "material:BEEHIVE" + sign: 'H' \ No newline at end of file diff --git a/src/main/resources/recipes/upgrade-2.yml b/src/main/resources/recipes/upgrade-2.yml new file mode 100644 index 0000000..6e2c6a6 --- /dev/null +++ b/src/main/resources/recipes/upgrade-2.yml @@ -0,0 +1,20 @@ +type: CRAFTING_SHAPED + +group: beeupgrade + +category: MISC + +pattern: + - "###" + - "#H#" + - "###" + +result: + item: "morebees:level-2" + amount: 1 + +ingredients: + - item: "material:IRON_INGOT" + sign: '#' + - item: "morebees:level-1" + sign: 'H' \ No newline at end of file diff --git a/src/main/resources/recipes/upgrade-3.yml b/src/main/resources/recipes/upgrade-3.yml new file mode 100644 index 0000000..6e1a4f6 --- /dev/null +++ b/src/main/resources/recipes/upgrade-3.yml @@ -0,0 +1,20 @@ +type: CRAFTING_SHAPED + +group: beeupgrade + +category: MISC + +pattern: + - "###" + - "#H#" + - "###" + +result: + item: "morebees:level-3" + amount: 1 + +ingredients: + - item: "material:GOLD_INGOT" + sign: '#' + - item: "morebees:level-2" + sign: 'H' \ No newline at end of file diff --git a/src/main/resources/upgrades.yml b/src/main/resources/upgrades.yml new file mode 100644 index 0000000..61241e7 --- /dev/null +++ b/src/main/resources/upgrades.yml @@ -0,0 +1,37 @@ +upgrades: +- id: level-1 + item: + material: COPPER_INGOT + name: Level 1 Upgrade + lore: + - This is level 1 beehive upgrade + - 'max bees: %max-bees%' + - 'production-multiplier: %production-multiplier%' + - 'produce-blocks: %produce-blocks%' + max-bees: 3 + production-multiplier: 1.5 + produce-blocks: false +- id: level-2 + item: + material: IRON_INGOT + name: Level 2 Upgrade + lore: + - This is level 2 beehive upgrade + - 'max bees: %max-bees%' + - 'production-multiplier: %production-multiplier%' + - 'produce-blocks: %produce-blocks%' + max-bees: 6 + production-multiplier: 2.0 + produce-blocks: false +- id: level-3 + item: + material: GOLD_INGOT + name: Level 3 Upgrade + lore: + - This is level 3 beehive upgrade + - 'max bees: %max-bees%' + - 'production-multiplier: %production-multiplier%' + - 'produce-blocks: %produce-blocks%' + max-bees: 6 + production-multiplier: 1.0 + produce-blocks: true