diff --git a/.classpath b/.classpath index 157d5bc..e522f14 100644 --- a/.classpath +++ b/.classpath @@ -11,8 +11,6 @@ - - @@ -25,7 +23,7 @@ - - + + diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml index c27ffc4..29b7c77 100644 --- a/dependency-reduced-pom.xml +++ b/dependency-reduced-pom.xml @@ -3,7 +3,7 @@ 4.0.0 CustomRecipes CustomRecipes - 2.0.5.1 + 2.0.5.6 src @@ -14,6 +14,22 @@ + + maven-javadoc-plugin + 3.5.0 + + + + javadoc + + + + + false + true + me.mehboss.recipe.* + + maven-compiler-plugin 3.8.1 @@ -34,23 +50,24 @@ + true *:* - - com/cryptomorin/xseries/XBiome* - com/cryptomorin/xseries/XBlock* - com/cryptomorin/xseries/XEntity* - com/cryptomorin/xseries/XEntityType* - com/cryptomorin/xseries/XItemStack* - com/cryptomorin/xseries/XWorldBorder* - com/cryptomorin/xseries/XTag* - com/cryptomorin/xseries/NMSExtras* - com/cryptomorin/xseries/NoteBlockMusic* - com/cryptomorin/xseries/SkullCacheListener* - + + + de.tr7zw.changeme.nbtapi + me.mehboss.utils.nbtapi + + + com.cryptomorin.xseries + me.mehboss.shaded.xseries + + + ${project.artifactId}-${project.version} + false @@ -207,5 +224,11 @@ 6.0.54 provided + + com.github.Valorless + HavenBags + 156279014a + provided + diff --git a/docs/allclasses-index.html b/docs/allclasses-index.html new file mode 100644 index 0000000..37209df --- /dev/null +++ b/docs/allclasses-index.html @@ -0,0 +1,207 @@ + + + + +All Classes and Interfaces (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

All Classes and Interfaces

+
+
+
+
+
+
Class
+
Description
+ +
+
Represents a brewing action to perform when brewing completes.
+
+ +
+
Handles the registration and execution of custom brewing recipes.
+
+ +
+
Handles custom brewing logic: - Allows non-standard items in brewing stands - + Prevents item disappearance with shift-clicks - Automatically starts brewing + when valid custom recipes are present - Supports both empty-slot and + bottle-required brewing
+
+ +
+
Handles runtime logic for custom brewing recipes.
+
+ +
+
Stores all extra data required for a custom brewing-stand recipe.
+
+ +
 
+ +
 
+ +
 
+ +
 
+ +
 
+ +
 
+ +
 
+ +
 
+ +
 
+ +
 
+ +
 
+ +
 
+ +
 
+ +
 
+ +
+
Utility class providing cross-version compatibility helpers that rely on + reflection to handle differences between Minecraft / Bukkit versions.
+
+ +
+
Stores extra data for custom furnace-based recipes.
+
+ +
+
Manages cooldown timers for players on a per-recipe basis.
+
+ +
+
Represents a cooldown timer for a specific recipe.
+
+ +
+
Stores additional data for custom crafting recipes.
+
+ +
 
+ +
+
Thrown when a recipe fails validation or contains invalid data.
+
+ +
+
Utility class for handling various inventory-related operations such as: + + Custom click interaction behavior + Stack merging logic + Space calculations for inventory storage + Detecting empty inventory slots +
+
+ +
 
+ +
+
Handles all metadata comparison logic used during special recipe matching.
+
+ +
+
Represents all optional conditions that may be required for a recipe + to be considered valid before crafting/smelting/brewing/etc.
+
+ +
+
Conditions related to the world or block environment in which + the recipe is executed.
+
+ +
+
Represents a range of valid in-game times for recipe conditions.
+
+ +
+
The full condition set for a recipe.
+
+ +
+
Utility manager and API entry point for creating, validating, and storing + custom recipe definitions inside the CustomRecipes plugin.
+
+ +
+
Represents a single ingredient used in a recipe.
+
+ +
+
Represents a fully defined custom recipe used by the CustomRecipes API.
+
+ +
 
+ +
 
+ +
+
Stores additional data for workstation-based recipes such as + anvils or stonecutters.
+
+
+
+
+
+
+ +
+
+
+ + diff --git a/docs/allpackages-index.html b/docs/allpackages-index.html new file mode 100644 index 0000000..2f5a5c8 --- /dev/null +++ b/docs/allpackages-index.html @@ -0,0 +1,90 @@ + + + + +All Packages (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

All Packages

+
+
Package Summary
+
+
Package
+
Description
+ +
+
Handles custom brewing stand recipes and brewing mechanics.
+
+ +
+
Provides all command handlers for the plugin, including administrative + commands, player utilities, debug tools, and recipe management commands.
+
+ +
+
Core recipe management system.
+
+ +
+
Contains general-purpose utility classes used throughout the plugin.
+
+ +
+
Contains data models for defining custom recipe behavior.
+
+
+
+
+ +
+
+
+ + diff --git a/docs/deprecated-list.html b/docs/deprecated-list.html new file mode 100644 index 0000000..c27942e --- /dev/null +++ b/docs/deprecated-list.html @@ -0,0 +1,109 @@ + + + + +Deprecated List (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Deprecated API

+
+

Contents

+ + + +
+
+ +
+
+
+ + diff --git a/docs/element-list b/docs/element-list new file mode 100644 index 0000000..54cb3f2 --- /dev/null +++ b/docs/element-list @@ -0,0 +1,5 @@ +me.mehboss.brewing +me.mehboss.commands +me.mehboss.recipe +me.mehboss.utils +me.mehboss.utils.data diff --git a/docs/help-doc.html b/docs/help-doc.html new file mode 100644 index 0000000..31bbebd --- /dev/null +++ b/docs/help-doc.html @@ -0,0 +1,199 @@ + + + + +API Help (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+

JavaDoc Help

+
+
+

Navigation

+Starting from the Overview page, you can browse the documentation using the links in each page, and in the navigation bar at the top of each page. The Index and Search box allow you to navigate to specific declarations and summary pages, including: All Packages, All Classes and Interfaces + +
+
+
+

Kinds of Pages

+The following sections describe the different kinds of pages in this collection. +
+

Overview

+

The Overview page is the front page of this API document and provides a list of all packages with a summary for each. This page can also contain an overall description of the set of packages.

+
+
+

Package

+

Each package has a page that contains a list of its classes and interfaces, with a summary for each. These pages may contain the following categories:

+
    +
  • Interfaces
  • +
  • Classes
  • +
  • Enum Classes
  • +
  • Exception Classes
  • +
  • Annotation Interfaces
  • +
+
+
+

Class or Interface

+

Each class, interface, nested class and nested interface has its own separate page. Each of these pages has three sections consisting of a declaration and description, member summary tables, and detailed member descriptions. Entries in each of these sections are omitted if they are empty or not applicable.

+
    +
  • Class Inheritance Diagram
  • +
  • Direct Subclasses
  • +
  • All Known Subinterfaces
  • +
  • All Known Implementing Classes
  • +
  • Class or Interface Declaration
  • +
  • Class or Interface Description
  • +
+
+
    +
  • Nested Class Summary
  • +
  • Enum Constant Summary
  • +
  • Field Summary
  • +
  • Property Summary
  • +
  • Constructor Summary
  • +
  • Method Summary
  • +
  • Required Element Summary
  • +
  • Optional Element Summary
  • +
+
+
    +
  • Enum Constant Details
  • +
  • Field Details
  • +
  • Property Details
  • +
  • Constructor Details
  • +
  • Method Details
  • +
  • Element Details
  • +
+

Note: Annotation interfaces have required and optional elements, but not methods. Only enum classes have enum constants. The components of a record class are displayed as part of the declaration of the record class. Properties are a feature of JavaFX.

+

The summary entries are alphabetical, while the detailed descriptions are in the order they appear in the source code. This preserves the logical groupings established by the programmer.

+
+
+

Other Files

+

Packages and modules may contain pages with additional information related to the declarations nearby.

+
+
+

Use

+

Each documented package, class or interface has its own Use page, which lists packages, classes, interfaces, methods, constructors and fields that use any part of that package, class or interface. Given a class or interface A, its Use page includes subclasses or subinterfaces of A, fields declared as A, methods that return A, methods and constructors with parameters of type A, and subclasses or subinterfaces with parameters of type A. You can access this page by first going to the package, class or interface, then clicking on the USE link in the navigation bar.

+
+
+

Tree (Class Hierarchy)

+

There is a Class Hierarchy page for all packages, plus a hierarchy for each package. Each hierarchy page contains a list of classes and a list of interfaces. Classes are organized by inheritance structure starting with java.lang.Object. Interfaces do not inherit from java.lang.Object.

+
    +
  • When viewing the Overview page, clicking on TREE displays the hierarchy for all packages.
  • +
  • When viewing a particular package, class or interface page, clicking on TREE displays the hierarchy for only that package.
  • +
+
+
+

Deprecated API

+

The Deprecated API page lists all of the API that have been deprecated. A deprecated API is not recommended for use, generally due to shortcomings, and a replacement API is usually given. Deprecated APIs may be removed in future implementations.

+
+
+

Serialized Form

+

Each serializable or externalizable class has a description of its serialization fields and methods. This information is of interest to those who implement rather than use the API. While there is no link in the navigation bar, you can get to this information by going to any serialized class and clicking "Serialized Form" in the "See Also" section of the class description.

+
+
+

All Packages

+

The All Packages page contains an alphabetic index of all packages contained in the documentation.

+
+
+

All Classes and Interfaces

+

The All Classes and Interfaces page contains an alphabetic index of all classes and interfaces contained in the documentation, including annotation interfaces, enum classes, and record classes.

+
+
+

Index

+

The Index contains an alphabetic index of all classes, interfaces, constructors, methods, and fields in the documentation, as well as summary pages such as All Packages, All Classes and Interfaces.

+
+
+
+This help file applies to API documentation generated by the standard doclet. +
+
+ +
+
+
+ + diff --git a/docs/index-all.html b/docs/index-all.html new file mode 100644 index 0000000..fafcde2 --- /dev/null +++ b/docs/index-all.html @@ -0,0 +1,1233 @@ + + + + +Index (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Index

+
+A B C D E F G H I K L M O P R S T V W 
All Classes and Interfaces|All Packages|Serialized Form +

A

+
+
addCooldown(UUID, CooldownManager.Cooldown) - Method in class me.mehboss.utils.CooldownManager
+
+
Adds a cooldown for a specific player.
+
+
addDisabledWorld(String) - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Adds a world to the noCraft list
+
+
addIngredient(RecipeUtil.Ingredient) - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Adds an Ingredient object to the recipe, required Requires 9 ingredients for + crafting, 1 otherwise
+
+
addLeftoverItem(String) - Method in class me.mehboss.utils.data.CraftingRecipeData
+
+
Setter for adding an ingredient as a leftover
+
+
addMaterialChoice(Material) - Method in class me.mehboss.utils.RecipeUtil.Ingredient
+
+
Adds an additional valid Material for this ingredient.
+
+
advancements - Variable in class me.mehboss.utils.RecipeConditions.Block
+
 
+
all - Variable in class me.mehboss.utils.RecipeConditions.ConditionSet
+
+
“conditions” or “conditions_all” block (AND).
+
+
allowBiomes - Variable in class me.mehboss.utils.RecipeConditions.Block
+
 
+
allowWorlds - Variable in class me.mehboss.utils.RecipeConditions.Block
+
 
+
ANVIL - Enum constant in enum class me.mehboss.utils.RecipeUtil.Recipe.RecipeType
+
 
+
any - Variable in class me.mehboss.utils.RecipeConditions.ConditionSet
+
+
“conditions_any” is a list of OR blocks.
+
+
args - Variable in class me.mehboss.commands.CRCommand
+
 
+
asString() - Method in class me.mehboss.utils.RecipeConditions.Block.TimeRange
+
 
+
+

B

+
+
BLASTFURNACE - Enum constant in enum class me.mehboss.utils.RecipeUtil.Recipe.RecipeType
+
 
+
Block() - Constructor for class me.mehboss.utils.RecipeConditions.Block
+
 
+
brew(BrewerInventory, ItemStack, ItemStack) - Method in class me.mehboss.brewing.BrewAction
+
+
Called when brewing completes.
+
+
BrewAction - Class in me.mehboss.brewing
+
+
Represents a brewing action to perform when brewing completes.
+
+
BrewAction() - Constructor for class me.mehboss.brewing.BrewAction
+
 
+
Brewery - Class in me.mehboss.brewing
+
+
Handles the registration and execution of custom brewing recipes.
+
+
Brewery(JavaPlugin) - Constructor for class me.mehboss.brewing.Brewery
+
+
Creates a new Brewery manager and registers it as an event listener.
+
+
BrewEvent - Class in me.mehboss.brewing
+
+
Handles custom brewing logic: - Allows non-standard items in brewing stands - + Prevents item disappearance with shift-clicks - Automatically starts brewing + when valid custom recipes are present - Supports both empty-slot and + bottle-required brewing
+
+
BrewEvent() - Constructor for class me.mehboss.brewing.BrewEvent
+
 
+
BREWING_STAND - Enum constant in enum class me.mehboss.utils.RecipeUtil.Recipe.RecipeType
+
 
+
BrewingRecipe - Class in me.mehboss.brewing
+
+
Handles runtime logic for custom brewing recipes.
+
+
BrewingRecipe() - Constructor for class me.mehboss.brewing.BrewingRecipe
+
 
+
BrewingRecipeData - Class in me.mehboss.utils.data
+
+
Stores all extra data required for a custom brewing-stand recipe.
+
+
BrewingRecipeData(String) - Constructor for class me.mehboss.utils.data.BrewingRecipeData
+
 
+
+

C

+
+
calculateClickedSlot(InventoryClickEvent) - Static method in class me.mehboss.utils.InventoryUtils
+
+
Wrapper method that reads the cursor and clicked item directly from the event.
+
+
calculateClickedSlot(InventoryClickEvent, ItemStack, ItemStack) - Static method in class me.mehboss.utils.InventoryUtils
+
+
Manually computes the behavior of clicking inside an inventory, overriding + Bukkit's default logic.
+
+
CAMPFIRE - Enum constant in enum class me.mehboss.utils.RecipeUtil.Recipe.RecipeType
+
 
+
cleanExpired() - Method in class me.mehboss.utils.CooldownManager
+
+
Cleans all expired cooldowns from memory.
+
+
command - Variable in class me.mehboss.commands.CRCommand
+
 
+
CommandBook - Class in me.mehboss.commands
+
 
+
CommandBook() - Constructor for class me.mehboss.commands.CommandBook
+
 
+
CommandCrafterDebug - Class in me.mehboss.commands
+
 
+
CommandCrafterDebug() - Constructor for class me.mehboss.commands.CommandCrafterDebug
+
 
+
CommandDebug - Class in me.mehboss.commands
+
 
+
CommandDebug() - Constructor for class me.mehboss.commands.CommandDebug
+
 
+
CommandEditItem - Class in me.mehboss.commands
+
 
+
CommandEditItem() - Constructor for class me.mehboss.commands.CommandEditItem
+
 
+
CommandGive - Class in me.mehboss.commands
+
 
+
CommandGive() - Constructor for class me.mehboss.commands.CommandGive
+
 
+
CommandGUI - Class in me.mehboss.commands
+
 
+
CommandGUI() - Constructor for class me.mehboss.commands.CommandGUI
+
 
+
CommandHelp - Class in me.mehboss.commands
+
 
+
CommandHelp() - Constructor for class me.mehboss.commands.CommandHelp
+
 
+
CommandItems - Class in me.mehboss.commands
+
 
+
CommandItems() - Constructor for class me.mehboss.commands.CommandItems
+
 
+
CommandList - Class in me.mehboss.commands
+
 
+
CommandList() - Constructor for class me.mehboss.commands.CommandList
+
 
+
CommandListener - Class in me.mehboss.recipe
+
 
+
CommandListener() - Constructor for class me.mehboss.recipe.CommandListener
+
 
+
CommandRecipe - Class in me.mehboss.commands
+
 
+
CommandRecipe() - Constructor for class me.mehboss.commands.CommandRecipe
+
 
+
CommandReload - Class in me.mehboss.commands
+
 
+
CommandReload() - Constructor for class me.mehboss.commands.CommandReload
+
 
+
CommandRemove - Class in me.mehboss.commands
+
 
+
CommandRemove() - Constructor for class me.mehboss.commands.CommandRemove
+
 
+
CommandShow - Class in me.mehboss.commands
+
 
+
CommandShow() - Constructor for class me.mehboss.commands.CommandShow
+
 
+
CompatibilityUtil - Class in me.mehboss.utils
+
+
Utility class providing cross-version compatibility helpers that rely on + reflection to handle differences between Minecraft / Bukkit versions.
+
+
CompatibilityUtil() - Constructor for class me.mehboss.utils.CompatibilityUtil
+
 
+
ConditionSet() - Constructor for class me.mehboss.utils.RecipeConditions.ConditionSet
+
 
+
contains(int) - Method in class me.mehboss.utils.RecipeConditions.Block.TimeRange
+
 
+
CookingRecipeData - Class in me.mehboss.utils.data
+
+
Stores extra data for custom furnace-based recipes.
+
+
CookingRecipeData(String) - Constructor for class me.mehboss.utils.data.CookingRecipeData
+
 
+
Cooldown(String, long) - Constructor for class me.mehboss.utils.CooldownManager.Cooldown
+
+
Creates a new cooldown timer starting immediately.
+
+
cooldownManager - Variable in class me.mehboss.recipe.Main
+
 
+
CooldownManager - Class in me.mehboss.utils
+
+
Manages cooldown timers for players on a per-recipe basis.
+
+
CooldownManager() - Constructor for class me.mehboss.utils.CooldownManager
+
 
+
CooldownManager.Cooldown - Class in me.mehboss.utils
+
+
Represents a cooldown timer for a specific recipe.
+
+
crafterdebug - Variable in class me.mehboss.recipe.Main
+
 
+
CraftingRecipeData - Class in me.mehboss.utils.data
+
+
Stores additional data for custom crafting recipes.
+
+
CraftingRecipeData(String) - Constructor for class me.mehboss.utils.data.CraftingRecipeData
+
 
+
CRCommand - Class in me.mehboss.commands
+
 
+
CRCommand(CommandSender, Command, String, String[]) - Constructor for class me.mehboss.commands.CRCommand
+
 
+
createRecipe(RecipeUtil.Recipe) - Method in class me.mehboss.utils.RecipeUtil
+
+
Adds a finished Recipe object to the API
+
+
customConfig - Variable in class me.mehboss.recipe.Main
+
 
+
+

D

+
+
debounceMap - Variable in class me.mehboss.recipe.Main
+
 
+
debug - Variable in class me.mehboss.recipe.Main
+
 
+
denyBiomes - Variable in class me.mehboss.utils.RecipeConditions.Block
+
 
+
denyWorlds - Variable in class me.mehboss.utils.RecipeConditions.Block
+
 
+
disabledrecipe - Variable in class me.mehboss.recipe.Main
+
 
+
+

E

+
+
extractUrlFromBase64(String) - Static method in class me.mehboss.utils.CompatibilityUtil
+
+
Extracts the skin texture URL from a Base64-encoded JSON texture payload.
+
+
+

F

+
+
fromString(String) - Static method in enum class me.mehboss.utils.RecipeUtil.Recipe.RecipeType
+
 
+
FURNACE - Enum constant in enum class me.mehboss.utils.RecipeUtil.Recipe.RecipeType
+
 
+
+

G

+
+
getAbbreviation() - Method in class me.mehboss.utils.RecipeUtil.Ingredient
+
+
Getter for the abbreviation of the ingredient, like G for grass Used when + shaping the recipe
+
+
getAllIngredientTypes() - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Compiles all ingredient materials of a recipe
+
+
getAllRecipes() - Method in class me.mehboss.utils.RecipeUtil
+
+
Getter for all recipes registered
+
+
getAllRecipesSortedByResult(ItemStack) - Method in class me.mehboss.utils.RecipeUtil
+
+
Get all recipes, ordered so that any recipes whose result is similar to the + given result item are first.
+
+
getAllResults() - Method in class me.mehboss.utils.RecipeUtil
+
+
Getter for all recipe results
+
+
getAmount() - Method in class me.mehboss.utils.RecipeUtil.Ingredient
+
+
Getter for the amount requirement
+
+
getBookCategory() - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Getter for setBookCategory
+
+
getBrewAction() - Method in class me.mehboss.utils.data.BrewingRecipeData
+
+
Gets the brew action executed when brewing finishes.
+
+
getBrewFuel() - Method in class me.mehboss.utils.data.BrewingRecipeData
+
+
Gets the brewing fuel item.
+
+
getBrewFuelCharge() - Method in class me.mehboss.utils.data.BrewingRecipeData
+
+
Gets how much fuel is consumed per brew cycle.
+
+
getBrewFuelSet() - Method in class me.mehboss.utils.data.BrewingRecipeData
+
+
Gets how much internal fuel is added per fuel item.
+
+
getBrewFuelSlot() - Method in class me.mehboss.utils.data.BrewingRecipeData
+
+
Gets the slot index used for the fuel.
+
+
getBrewIngredient() - Method in class me.mehboss.utils.data.BrewingRecipeData
+
+
Gets the brewing ingredient item.
+
+
getBrewIngredientSlot() - Method in class me.mehboss.utils.data.BrewingRecipeData
+
+
Gets the slot index used for the ingredient.
+
+
getCommand() - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Gets the commands to perform upon crafting a recipe
+
+
getConditionSet() - Method in class me.mehboss.utils.data.CraftingRecipeData
+
+
Gets the ConditionSet attached to this recipe.
+
+
getCookTime() - Method in class me.mehboss.utils.data.CookingRecipeData
+
+
Gets how long the recipe takes to cook.
+
+
getCooldown() - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Getter for the cooldown
+
+
getCooldown(UUID, String) - Method in class me.mehboss.utils.CooldownManager
+
+
Retrieves a cooldown for a player and recipe.
+
+
getCooldowns() - Method in class me.mehboss.utils.CooldownManager
+
 
+
getCustomItemID() - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Getter for the custom ID output
+
+
getCustomItemPlugin(String) - Method in class me.mehboss.utils.RecipeUtil
+
+
Extracts the plugin namespace from a custom item key.
+
+
getCustomModelData() - Method in class me.mehboss.utils.RecipeUtil.Ingredient
+
+
Getter for an ingredients model data
+
+
getDisabledWorlds() - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Getter for worlds the recipe can not be crafted in
+
+
getDisplayname(ItemMeta) - Static method in class me.mehboss.utils.CompatibilityUtil
+
+
Retrieves the display name from an item.
+
+
getDisplayName() - Method in class me.mehboss.utils.RecipeUtil.Ingredient
+
+
Getter for the ingredient displayname
+
+
getDuration() - Method in class me.mehboss.utils.CooldownManager.Cooldown
+
 
+
getExperience() - Method in class me.mehboss.utils.data.CookingRecipeData
+
+
Gets the experience value awarded when the item is cooked.
+
+
getExperience() - Method in class me.mehboss.utils.data.WorkstationRecipeData
+
+
Gets the experience reward assigned to this recipe.
+
+
getGroup() - Method in class me.mehboss.utils.data.CraftingRecipeData
+
+
Getter for the recipe group.
+
+
getGroup() - Method in class me.mehboss.utils.data.WorkstationRecipeData
+
+
Gets the group tag assigned to this recipe.
+
+
getIdentifier() - Method in class me.mehboss.utils.RecipeUtil.Ingredient
+
+
Getter for an ingredients identifier
+
+
getIgnoreData() - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Getter for ignoreData
+
+
getIgnoreModelData() - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Getter for ignoreModelData
+
+
getIgnoreNames() - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Getter for ignoreNames
+
+
getIngredients() - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Getter for the recipe ingredients
+
+
getIngredientSize() - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Getter for the amount of ingredients the recipe has
+
+
getInstance() - Static method in class me.mehboss.brewing.Brewery
+
 
+
getInstance() - Static method in class me.mehboss.recipe.Main
+
 
+
getInventorySpace(Player, ItemStack) - Static method in class me.mehboss.utils.InventoryUtils
+
+
Computes available space for a specific item in a player's inventory.
+
+
getInventorySpace(Inventory, ItemStack) - Static method in class me.mehboss.utils.InventoryUtils
+
+
Computes available space in an inventory for one specific item type.
+
+
getInventorySpace(ItemStack[], ItemStack) - Static method in class me.mehboss.utils.InventoryUtils
+
+
Computes total available capacity for an item based on empty slots and + partially filled stacks that match.
+
+
getInventoryView(InventoryEvent) - Static method in class me.mehboss.utils.CompatibilityUtil
+
+
Retrieves the InventoryView from an InventoryEvent using reflection.
+
+
getItem() - Method in class me.mehboss.utils.RecipeUtil.Ingredient
+
+
Getter for an ingredient itemstack
+
+
getKey() - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Getter for the NamedspaceKey
+
+
getKeyFromResult(ItemStack) - Method in class me.mehboss.utils.RecipeUtil
+
+
Getter for a key from the result ItemStack
+
+
getMaterial() - Method in class me.mehboss.utils.RecipeUtil.Ingredient
+
+
Getter for the material of the ingredient
+
+
getMaterialChoices() - Method in class me.mehboss.utils.RecipeUtil.Ingredient
+
+
Gets all additional materials that may satisfy this ingredient.
+
+
getMaterialData() - Method in class me.mehboss.utils.RecipeUtil.Ingredient
+
+
Deprecated. +
Use modern RecipeChoice or + Material APIs instead. MaterialData was + removed after Minecraft 1.13.
+
+
+
getMethod(Class<?>, String, Class<?>...) - Static method in class me.mehboss.utils.CompatibilityUtil
+
 
+
getMoonPhase(World) - Static method in class me.mehboss.utils.RecipeConditions
+
+
Calculates the current moon phase for the given world.
+
+
getName() - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Getter for the recipe name
+
+
getPerm() - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Getter for the permission needed to craft the recipe, can be null
+
+
getPlayerFromView(Object) - Static method in class me.mehboss.utils.CompatibilityUtil
+
+
Extracts the Player from an InventoryView instance using reflection.
+
+
getPlugin() - Method in class me.mehboss.brewing.Brewery
+
 
+
getRecipe(String) - Method in class me.mehboss.utils.RecipeUtil
+
+
Getter for a recipe from the Name
+
+
getRecipe(BrewerInventory) - Static method in class me.mehboss.brewing.BrewingRecipe
+
+
Attempts to locate a brewing recipe that matches the given brewer inventory.
+
+
getRecipeFromFurnaceSource(ItemStack) - Method in class me.mehboss.utils.RecipeUtil
+
+
Getter for a furnace recipe by the source
+
+
getRecipeFromKey(String) - Method in class me.mehboss.utils.RecipeUtil
+
+
Getter for a recipe from Namespacedkey
+
+
getRecipeFromResult(ItemStack) - Method in class me.mehboss.utils.RecipeUtil
+
+
Getter for a recipe from the result ItemStack
+
+
getRecipeID() - Method in class me.mehboss.utils.CooldownManager.Cooldown
+
 
+
getRecipeNames() - Method in class me.mehboss.utils.RecipeUtil
+
+
Getter for all recipe names
+
+
getRecipesFromType(RecipeUtil.Recipe.RecipeType) - Method in class me.mehboss.utils.RecipeUtil
+
+
Getter for all recipes matching a type
+
+
getRecipeUtil() - Method in class me.mehboss.recipe.Main
+
 
+
getRepairCost() - Method in class me.mehboss.utils.data.WorkstationRecipeData
+
+
Gets the repair cost assigned to this workstation recipe.
+
+
getRequiredBottleType() - Method in class me.mehboss.utils.data.BrewingRecipeData
+
+
Gets the required bottle type for this recipe.
+
+
getResult() - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Getter for the recipe result
+
+
getResultFromKey(String) - Method in class me.mehboss.utils.RecipeUtil
+
+
Getter for a result from a namespaced key.
+
+
getRow(int) - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Getter for the shaped recipe rows
+
+
getSlot() - Method in class me.mehboss.utils.RecipeUtil.Ingredient
+
+
Getter for the ingredient slot
+
+
getSlot(int) - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Getter for an ingredient slot
+
+
getSource() - Method in class me.mehboss.utils.data.CookingRecipeData
+
+
Gets the input item used by this cooking recipe.
+
+
getStartTime() - Method in class me.mehboss.utils.CooldownManager.Cooldown
+
 
+
getTimeLeft() - Method in class me.mehboss.utils.CooldownManager.Cooldown
+
+
Calculates how many seconds remain.
+
+
getTimeLeft(UUID, String) - Method in class me.mehboss.utils.CooldownManager
+
+
Gets the number of seconds left on a cooldown.
+
+
getTitle(InventoryEvent) - Static method in class me.mehboss.utils.CompatibilityUtil
+
+
Retrieves an inventory's title reflectively.
+
+
getTopInventory(InventoryEvent) - Static method in class me.mehboss.utils.CompatibilityUtil
+
+
Retrieves the top inventory from an InventoryEvent using reflection.
+
+
getType() - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Getter for the RecipeType
+
+
GRINDSTONE - Enum constant in enum class me.mehboss.utils.RecipeUtil.Recipe.RecipeType
+
 
+
+

H

+
+
hasAE - Variable in class me.mehboss.recipe.Main
+
 
+
hasCommands() - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Gets whether the recipe is an item or a command
+
+
hasCooldown() - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Getter for if the recipe has a cooldown set
+
+
hasCooldown(UUID, String) - Method in class me.mehboss.utils.CooldownManager
+
+
Checks whether a cooldown is active for the given player and recipe.
+
+
hasCustomModelData() - Method in class me.mehboss.utils.RecipeUtil.Ingredient
+
+
Getter for if an ingredient has CMD
+
+
hasDisplayname(ItemMeta) - Static method in class me.mehboss.utils.CompatibilityUtil
+
+
Checks if an item has a display name.
+
+
hasDisplayName() - Method in class me.mehboss.utils.RecipeUtil.Ingredient
+
+
Getter for if the ingredient has a displayname set
+
+
hasEE - Variable in class me.mehboss.recipe.Main
+
 
+
hasEmptySpaces(Player, int) - Static method in class me.mehboss.utils.InventoryUtils
+
+
Checks whether a player has at least a specified number of empty slots.
+
+
hasHavenBags - Variable in class me.mehboss.recipe.Main
+
 
+
hasIdentifier() - Method in class me.mehboss.utils.RecipeUtil.Ingredient
+
+
Getter for if the ingredient has an identifier set
+
+
hasInventorySpace(Player, ItemStack) - Static method in class me.mehboss.utils.InventoryUtils
+
+
Checks if a player has room for the given item.
+
+
hasInventorySpace(Inventory, ItemStack) - Static method in class me.mehboss.utils.InventoryUtils
+
+
Checks if an inventory has space for one stack of the given item.
+
+
hasInventorySpace(Inventory, ItemStack, int) - Static method in class me.mehboss.utils.InventoryUtils
+
+
Checks if an inventory has room for a given number of a specific item.
+
+
hasInventorySpace(ItemStack[], ItemStack) - Static method in class me.mehboss.utils.InventoryUtils
+
+
Checks if inventory contents have enough space for the given item.
+
+
hasKey() - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Getter for if the recipe has a NamedspaceKey
+
+
hasMaterialChoices() - Method in class me.mehboss.utils.RecipeUtil.Ingredient
+
+
Checks whether this ingredient has multiple valid materials.
+
+
hasMaterialData() - Method in class me.mehboss.utils.RecipeUtil.Ingredient
+
+
Deprecated. +
Use modern RecipeChoice or + Material APIs instead. MaterialData was + removed after Minecraft 1.13.
+
+
+
hasPerm() - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
 
+
hasRepairCost() - Method in class me.mehboss.utils.data.WorkstationRecipeData
+
+
Checks whether this recipe has a defined repair cost.
+
+
+

I

+
+
Ingredient(String, Material) - Constructor for class me.mehboss.utils.RecipeUtil.Ingredient
+
+
Parameterized constructor.
+
+
inInventory - Variable in class me.mehboss.recipe.Main
+
 
+
InvalidRecipeException - Exception Class in me.mehboss.utils
+
+
Thrown when a recipe fails validation or contains invalid data.
+
+
InvalidRecipeException(String) - Constructor for exception class me.mehboss.utils.InvalidRecipeException
+
+
Constructs a new InvalidRecipeException with a descriptive error message.
+
+
InventoryUtils - Class in me.mehboss.utils
+
+
Utility class for handling various inventory-related operations such as: + + Custom click interaction behavior + Stack merging logic + Space calculations for inventory storage + Detecting empty inventory slots +
+
+
InventoryUtils() - Constructor for class me.mehboss.utils.InventoryUtils
+
 
+
invokeMethod(Method, Object, Object...) - Static method in class me.mehboss.utils.CompatibilityUtil
+
 
+
isActive() - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Getter for setActive.
+
+
isAirOrNull(ItemStack) - Static method in class me.mehboss.utils.InventoryUtils
+
+
Checks if an item is null or AIR.
+
+
isBrewPerfect() - Method in class me.mehboss.utils.data.BrewingRecipeData
+
+
Checks if ingredient and fuel must match exactly.
+
+
isCustomItem() - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Getter for whether or not a recipe result is of a custom item
+
+
isCustomItem(String) - Method in class me.mehboss.utils.RecipeUtil
+
+
Checks whether a namespace belongs to a supported custom-item plugin.
+
+
isCustomTagged() - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Getter for checking if the recipe is tagged with the key
+
+
isDiscoverable() - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Getter for setDiscoverable
+
+
isEmpty() - Method in class me.mehboss.utils.RecipeConditions.Block
+
 
+
isEmpty() - Method in class me.mehboss.utils.RecipeConditions.ConditionSet
+
 
+
isEmpty() - Method in class me.mehboss.utils.RecipeUtil.Ingredient
+
+
Getter for checking if the ingredient is empty
+
+
isExactChoice() - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Getter for setExactChoice
+
+
isExpired() - Method in class me.mehboss.utils.CooldownManager.Cooldown
+
+
Checks whether the cooldown has completed.
+
+
isGrantItem() - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Gets whether the recipe output should be granted after a command is performed
+
+
isInt(String) - Static method in class me.mehboss.commands.CommandGive
+
 
+
isLeftover(String) - Method in class me.mehboss.utils.data.CraftingRecipeData
+
+
Checks if a MATERIAL is marked as leftover.
+
+
isPlaceable() - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Getter for setPlaceable
+
+
itemsMatch(String, ItemStack, RecipeUtil.Ingredient) - Method in class me.mehboss.utils.MetaChecks
+
+
Compares an input item against a recipe ingredient definition.
+
+
+

K

+
+
Key features: - Search tag in class me.mehboss.utils.RecipeUtil.Recipe
+
Section
+
+

L

+
+
label - Variable in class me.mehboss.commands.CRCommand
+
 
+
+

M

+
+
Main - Class in me.mehboss.recipe
+
 
+
Main() - Constructor for class me.mehboss.recipe.Main
+
 
+
max - Variable in class me.mehboss.utils.RecipeConditions.Block.TimeRange
+
 
+
me.mehboss.brewing - package me.mehboss.brewing
+
+
Handles custom brewing stand recipes and brewing mechanics.
+
+
me.mehboss.commands - package me.mehboss.commands
+
+
Provides all command handlers for the plugin, including administrative + commands, player utilities, debug tools, and recipe management commands.
+
+
me.mehboss.recipe - package me.mehboss.recipe
+
+
Core recipe management system.
+
+
me.mehboss.utils - package me.mehboss.utils
+
+
Contains general-purpose utility classes used throughout the plugin.
+
+
me.mehboss.utils.data - package me.mehboss.utils.data
+
+
Contains data models for defining custom recipe behavior.
+
+
metaChecks - Variable in class me.mehboss.recipe.Main
+
 
+
MetaChecks - Class in me.mehboss.utils
+
+
Handles all metadata comparison logic used during special recipe matching.
+
+
MetaChecks() - Constructor for class me.mehboss.utils.MetaChecks
+
 
+
min - Variable in class me.mehboss.utils.RecipeConditions.Block.TimeRange
+
 
+
moonPhases - Variable in class me.mehboss.utils.RecipeConditions.Block
+
 
+
+

O

+
+
onBrewClick(InventoryClickEvent) - Method in class me.mehboss.brewing.Brewery
+
+
Intercepts brewing stand clicks and evaluates whether custom brewing should begin.
+
+
onCommand(CommandSender, Command, String, String[]) - Method in class me.mehboss.recipe.CommandListener
+
 
+
onTabComplete(CommandSender, Command, String, String[]) - Method in class me.mehboss.commands.TabCompletion
+
 
+
+

P

+
+
parseBlock(ConfigurationSection) - Static method in class me.mehboss.utils.RecipeConditions
+
+
Parse a single AND block + (world/world_deny/biome/biome_deny/time/weather/advancement/moon_phase).
+
+
parseConditionSet(ConfigurationSection) - Static method in class me.mehboss.utils.RecipeConditions
+
+
Parse an entire condition set from the per-recipe section.
+
+
+

R

+
+
Recipe(String) - Constructor for class me.mehboss.utils.RecipeUtil.Recipe
+
+
Parameterized constructor.
+
+
recipeBook - Variable in class me.mehboss.recipe.Main
+
 
+
RecipeConditions - Class in me.mehboss.utils
+
+
Represents all optional conditions that may be required for a recipe + to be considered valid before crafting/smelting/brewing/etc.
+
+
RecipeConditions.Block - Class in me.mehboss.utils
+
+
Conditions related to the world or block environment in which + the recipe is executed.
+
+
RecipeConditions.Block.TimeRange - Class in me.mehboss.utils
+
+
Represents a range of valid in-game times for recipe conditions.
+
+
RecipeConditions.ConditionSet - Class in me.mehboss.utils
+
+
The full condition set for a recipe.
+
+
Recipe Registration - Search tag in class me.mehboss.utils.RecipeUtil
+
Section
+
recipeUtil - Variable in class me.mehboss.recipe.Main
+
 
+
RecipeUtil - Class in me.mehboss.utils
+
+
Utility manager and API entry point for creating, validating, and storing + custom recipe definitions inside the CustomRecipes plugin.
+
+
RecipeUtil() - Constructor for class me.mehboss.utils.RecipeUtil
+
 
+
RecipeUtil.Ingredient - Class in me.mehboss.utils
+
+
Represents a single ingredient used in a recipe.
+
+
RecipeUtil.Recipe - Class in me.mehboss.utils
+
+
Represents a fully defined custom recipe used by the CustomRecipes API.
+
+
RecipeUtil.Recipe.RecipeType - Enum Class in me.mehboss.utils
+
 
+
registerRecipe(RecipeUtil.Recipe) - Method in class me.mehboss.utils.RecipeUtil
+
+
Loads the specified recipe to the server.
+
+
reloadRecipes() - Method in class me.mehboss.utils.RecipeUtil
+
+
Resets and reloads all registered recipes, including CR recipes.
+
+
removeCooldown(UUID, String) - Method in class me.mehboss.utils.CooldownManager
+
+
Removes a cooldown for a specific recipe and player.
+
+
removeRecipe(String) - Static method in class me.mehboss.commands.CommandRemove
+
 
+
removeRecipe(String) - Method in class me.mehboss.utils.RecipeUtil
+
+
Removes a recipe registered with CR.
+
+
requiresBottles() - Method in class me.mehboss.utils.data.BrewingRecipeData
+
+
Checks whether bottle validation is required.
+
+
Run(CRCommand) - Static method in class me.mehboss.commands.CommandBook
+
 
+
Run(CRCommand) - Static method in class me.mehboss.commands.CommandCrafterDebug
+
 
+
Run(CRCommand) - Static method in class me.mehboss.commands.CommandDebug
+
 
+
Run(CRCommand) - Static method in class me.mehboss.commands.CommandEditItem
+
 
+
Run(CRCommand) - Static method in class me.mehboss.commands.CommandGive
+
 
+
Run(CRCommand) - Static method in class me.mehboss.commands.CommandGUI
+
 
+
Run(CRCommand) - Static method in class me.mehboss.commands.CommandHelp
+
 
+
Run(CRCommand) - Static method in class me.mehboss.commands.CommandItems
+
 
+
Run(CRCommand) - Static method in class me.mehboss.commands.CommandList
+
 
+
Run(CRCommand) - Static method in class me.mehboss.commands.CommandRecipe
+
 
+
Run(CRCommand) - Static method in class me.mehboss.commands.CommandReload
+
 
+
Run(CRCommand) - Static method in class me.mehboss.commands.CommandRemove
+
 
+
Run(CRCommand) - Static method in class me.mehboss.commands.CommandShow
+
 
+
+

S

+
+
saveInventory - Variable in class me.mehboss.recipe.Main
+
 
+
sender - Variable in class me.mehboss.commands.CRCommand
+
 
+
serverVersionAtLeast(int, int) - Method in class me.mehboss.recipe.Main
+
 
+
setActive(Boolean) - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Enables or disables a recipe
+
+
setAmount(int) - Method in class me.mehboss.utils.RecipeUtil.Ingredient
+
+
Setter for the amount requirement
+
+
setBookCategory(String) - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Sets the category the recipe will be shown within the vanilla recipe book.
+
+
setBrewAction(BrewAction) - Method in class me.mehboss.utils.data.BrewingRecipeData
+
+
Sets how this recipe behaves when brewing completes.
+
+
setBrewFuel(RecipeUtil.Ingredient) - Method in class me.mehboss.utils.data.BrewingRecipeData
+
+
Builds and stores the fuel item used for brewing.
+
+
setBrewFuelCharge(int) - Method in class me.mehboss.utils.data.BrewingRecipeData
+
+
Sets how much internal fuel is consumed per brewing cycle.
+
+
setBrewFuelSet(int) - Method in class me.mehboss.utils.data.BrewingRecipeData
+
+
Sets how much internal fuel is added when consuming a fuel item.
+
+
setBrewFuelSlot(int) - Method in class me.mehboss.utils.data.BrewingRecipeData
+
+
Sets which slot is used for the fuel.
+
+
setBrewIngredient(RecipeUtil.Ingredient) - Method in class me.mehboss.utils.data.BrewingRecipeData
+
+
Builds and stores the ingredient item used for brewing.
+
+
setBrewIngredientSlot(int) - Method in class me.mehboss.utils.data.BrewingRecipeData
+
+
Sets which slot is used for the ingredient.
+
+
setBrewPerfect(boolean) - Method in class me.mehboss.utils.data.BrewingRecipeData
+
+
Enables or disables exact matching for ingredient and fuel items.
+
+
setCommands(List<String>) - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Sets the commands to perform upon crafting a recipe
+
+
setConditionSet(RecipeConditions.ConditionSet) - Method in class me.mehboss.utils.data.CraftingRecipeData
+
+
Sets the ConditionSet for this recipe.
+
+
setCookTime(int) - Method in class me.mehboss.utils.data.CookingRecipeData
+
+
Sets the cook time in ticks.
+
+
setCooldown(long) - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Setter for the recipe cooldown
+
+
setCooldowns(Map<UUID, Map<String, CooldownManager.Cooldown>>) - Method in class me.mehboss.utils.CooldownManager
+
+
Restores cooldowns from a saved map.
+
+
setCustomItem(String) - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Sets an ID for custom item results
+
+
setCustomModelData(int) - Method in class me.mehboss.utils.RecipeUtil.Ingredient
+
+
Setter for an ingredients custom model data
+
+
setDiscoverable(Boolean) - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Sets whether or not a recipe is going to be discovered automatically
+
+
setDisplayname(ItemStack, String) - Static method in class me.mehboss.utils.CompatibilityUtil
+
+
Sets an item's display name using version-aware APIs.
+
+
setDisplayName(String) - Method in class me.mehboss.utils.RecipeUtil.Ingredient
+
+
Setter for the ingredients displayname
+
+
setExactChoice(Boolean) - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Sets whether or not a recipe is going to be using exactChoice.
+
+
setExperience(float) - Method in class me.mehboss.utils.data.CookingRecipeData
+
+
Sets the experience granted when the recipe is completed.
+
+
setExperience(float) - Method in class me.mehboss.utils.data.WorkstationRecipeData
+
+
Sets the amount of experience granted by this recipe.
+
+
setFieldValue(Object, String, Object) - Static method in class me.mehboss.utils.CompatibilityUtil
+
 
+
setGrantItem(Boolean) - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Sets whether the recipe output should be granted after a command is performed
+
+
setGroup(String) - Method in class me.mehboss.utils.data.CraftingRecipeData
+
+
Sets the group of the recipe.
+
+
setGroup(String) - Method in class me.mehboss.utils.data.WorkstationRecipeData
+
+
Sets the group this recipe belongs to.
+
+
setIdentifier(String) - Method in class me.mehboss.utils.RecipeUtil.Ingredient
+
+
Setter for the identifier of the ingredient Requires the ingredient to be + tagged with another Custom Recipe
+
+
setIgnoreData(boolean) - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Setter for if the recipe ignores MetaData
+
+
setIgnoreModelData(boolean) - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Setter for if the recipe ignores CustomModelData
+
+
setIgnoreNames(boolean) - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Setter for if the recipe ignores names
+
+
setItem(ItemStack) - Method in class me.mehboss.utils.RecipeUtil.Ingredient
+
+
Setter for the itemstack of the ingredient
+
+
setKey(String) - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Setter for the NamedspaceKey, required
+
+
setMaterial(Material) - Method in class me.mehboss.utils.RecipeUtil.Ingredient
+
+
Setter for the material of the ingredient + + This should NEVER be used, except internally!
+
+
setMaterialData(MaterialData) - Method in class me.mehboss.utils.RecipeUtil.Ingredient
+
+
Deprecated. +
Use modern RecipeChoice or + Material APIs instead. MaterialData was + removed after Minecraft 1.13.
+
+
+
setPerm(String) - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Setter for the permission needed to craft the recipe
+
+
setPlaceable(Boolean) - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Sets whether or not a recipe is allowed to be placed.
+
+
setRepairCost(int) - Method in class me.mehboss.utils.data.WorkstationRecipeData
+
+
Sets the repair cost for this recipe (used by anvil mechanics).
+
+
setRequiredBottleType(Material) - Method in class me.mehboss.utils.data.BrewingRecipeData
+
+
Sets the type of bottle required for valid brewing output.
+
+
setRequiresBottles(boolean) - Method in class me.mehboss.utils.data.BrewingRecipeData
+
+
Enables or disables bottle validation for brewing results.
+
+
setResult(ItemStack) - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Setter for the recipe result, required
+
+
setRow(int, String) - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Setter for the shaped recipe rows, required
+
+
setSlot(int) - Method in class me.mehboss.utils.RecipeUtil.Ingredient
+
+
Setter for the slot the ingredient is set to, not required for shapeless
+
+
setSource(ItemStack) - Method in class me.mehboss.utils.data.CookingRecipeData
+
+
Sets the input item that this recipe smelts or cooks.
+
+
setTagged(boolean) - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Setter for whether the result is tagged with the key
+
+
setType(RecipeUtil.Recipe.RecipeType) - Method in class me.mehboss.utils.RecipeUtil.Recipe
+
+
Setter for the RecipeType, not required and defaults to SHAPED
+
+
SHAPED - Enum constant in enum class me.mehboss.utils.RecipeUtil.Recipe.RecipeType
+
 
+
SHAPELESS - Enum constant in enum class me.mehboss.utils.RecipeUtil.Recipe.RecipeType
+
 
+
SMOKER - Enum constant in enum class me.mehboss.utils.RecipeUtil.Recipe.RecipeType
+
 
+
startBrewing(BrewerInventory, BrewingRecipeData) - Static method in class me.mehboss.brewing.BrewingRecipe
+
+
Begins a brewing cycle using the provided recipe.
+
+
STONECUTTER - Enum constant in enum class me.mehboss.utils.RecipeUtil.Recipe.RecipeType
+
 
+
SUPPORTED_PLUGINS - Variable in class me.mehboss.utils.RecipeUtil
+
 
+
+

T

+
+
TabCompletion - Class in me.mehboss.commands
+
 
+
TabCompletion() - Constructor for class me.mehboss.commands.TabCompletion
+
 
+
test(Location, Player) - Method in class me.mehboss.utils.RecipeConditions.Block
+
+
Fast-path test (no messages collected).
+
+
test(Location, Player) - Method in class me.mehboss.utils.RecipeConditions.ConditionSet
+
+
Fast-path test (no messages).
+
+
test(Location, Player, List<String>) - Method in class me.mehboss.utils.RecipeConditions.Block
+
+
Tests this block against a location and (optionally) a player.
+
+
test(Location, Player, List<String>) - Method in class me.mehboss.utils.RecipeConditions.ConditionSet
+
+
Overall logic: (all == null || all passes) AND (any is empty OR at least one + any-block passes) + + If outReasons is non-null, messages are added only for failing parts.
+
+
TimeRange(int, int) - Constructor for class me.mehboss.utils.RecipeConditions.Block.TimeRange
+
 
+
timeRanges - Variable in class me.mehboss.utils.RecipeConditions.Block
+
 
+
+

V

+
+
Validation rules include: - Search tag in class me.mehboss.utils.RecipeUtil
+
Section
+
valueOf(String) - Static method in enum class me.mehboss.utils.RecipeUtil.Recipe.RecipeType
+
+
Returns the enum constant of this class with the specified name.
+
+
values() - Static method in enum class me.mehboss.utils.RecipeUtil.Recipe.RecipeType
+
+
Returns an array containing the constants of this enum class, in +the order they are declared.
+
+
+

W

+
+
weathers - Variable in class me.mehboss.utils.RecipeConditions.Block
+
 
+
WorkstationRecipeData - Class in me.mehboss.utils.data
+
+
Stores additional data for workstation-based recipes such as + anvils or stonecutters.
+
+
WorkstationRecipeData(String) - Constructor for class me.mehboss.utils.data.WorkstationRecipeData
+
 
+
+A B C D E F G H I K L M O P R S T V W 
All Classes and Interfaces|All Packages|Serialized Form +
+
+ +
+
+
+ + diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..495e5d9 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,92 @@ + + + + +Overview (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

CustomRecipes 2.0.5.4 API

+
+
+
Packages
+
+
Package
+
Description
+ +
+
Handles custom brewing stand recipes and brewing mechanics.
+
+ +
+
Provides all command handlers for the plugin, including administrative + commands, player utilities, debug tools, and recipe management commands.
+
+ +
+
Core recipe management system.
+
+ +
+
Contains general-purpose utility classes used throughout the plugin.
+
+ +
+
Contains data models for defining custom recipe behavior.
+
+
+
+
+
+ +
+
+
+ + diff --git a/docs/javadoc.bat b/docs/javadoc.bat new file mode 100644 index 0000000..c5c47ce --- /dev/null +++ b/docs/javadoc.bat @@ -0,0 +1 @@ +cmd.exe /X /C "C:\Users\timot\.p2\pool\plugins\org.eclipse.justj.openjdk.hotspot.jre.full.win32.x86_64_23.0.0.v20240919-1706\jre\bin\javadoc.exe @options @packages" \ No newline at end of file diff --git a/docs/legal/ADDITIONAL_LICENSE_INFO b/docs/legal/ADDITIONAL_LICENSE_INFO new file mode 100644 index 0000000..ff700cd --- /dev/null +++ b/docs/legal/ADDITIONAL_LICENSE_INFO @@ -0,0 +1,37 @@ + ADDITIONAL INFORMATION ABOUT LICENSING + +Certain files distributed by Oracle America, Inc. and/or its affiliates are +subject to the following clarification and special exception to the GPLv2, +based on the GNU Project exception for its Classpath libraries, known as the +GNU Classpath Exception. + +Note that Oracle includes multiple, independent programs in this software +package. Some of those programs are provided under licenses deemed +incompatible with the GPLv2 by the Free Software Foundation and others. +For example, the package includes programs licensed under the Apache +License, Version 2.0 and may include FreeType. Such programs are licensed +to you under their original licenses. + +Oracle facilitates your further distribution of this package by adding the +Classpath Exception to the necessary parts of its GPLv2 code, which permits +you to use that code in combination with other independent modules not +licensed under the GPLv2. However, note that this would not permit you to +commingle code under an incompatible license with Oracle's GPLv2 licensed +code by, for example, cutting and pasting such code into a file also +containing Oracle's GPLv2 licensed code and then distributing the result. + +Additionally, if you were to remove the Classpath Exception from any of the +files to which it applies and distribute the result, you would likely be +required to license some or all of the other code in that distribution under +the GPLv2 as well, and since the GPLv2 is incompatible with the license terms +of some items included in the distribution by Oracle, removing the Classpath +Exception could therefore effectively compromise your ability to further +distribute the package. + +Failing to distribute notices associated with some files may also create +unexpected legal consequences. + +Proceed with caution and we recommend that you obtain the advice of a lawyer +skilled in open source matters before removing the Classpath Exception or +making modifications to this package which may subsequently be redistributed +and/or involve the use of third party software. diff --git a/docs/legal/ASSEMBLY_EXCEPTION b/docs/legal/ASSEMBLY_EXCEPTION new file mode 100644 index 0000000..4296666 --- /dev/null +++ b/docs/legal/ASSEMBLY_EXCEPTION @@ -0,0 +1,27 @@ + +OPENJDK ASSEMBLY EXCEPTION + +The OpenJDK source code made available by Oracle America, Inc. (Oracle) at +openjdk.org ("OpenJDK Code") is distributed under the terms of the GNU +General Public License version 2 +only ("GPL2"), with the following clarification and special exception. + + Linking this OpenJDK Code statically or dynamically with other code + is making a combined work based on this library. Thus, the terms + and conditions of GPL2 cover the whole combination. + + As a special exception, Oracle gives you permission to link this + OpenJDK Code with certain code licensed by Oracle as indicated at + https://openjdk.org/legal/exception-modules-2007-05-08.html + ("Designated Exception Modules") to produce an executable, + regardless of the license terms of the Designated Exception Modules, + and to copy and distribute the resulting executable under GPL2, + provided that the Designated Exception Modules continue to be + governed by the licenses under which they were offered by Oracle. + +As such, it allows licensees and sublicensees of Oracle's GPL2 OpenJDK Code +to build an executable that includes those portions of necessary code that +Oracle could not provide under GPL2 (or that Oracle has provided under GPL2 +with the Classpath exception). If you modify or add to the OpenJDK code, +that new GPL2 code may still be combined with Designated Exception Modules +if the new code is made subject to this exception by its copyright holder. diff --git a/docs/legal/LICENSE b/docs/legal/LICENSE new file mode 100644 index 0000000..8b400c7 --- /dev/null +++ b/docs/legal/LICENSE @@ -0,0 +1,347 @@ +The GNU General Public License (GPL) + +Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +Preamble + +The licenses for most software are designed to take away your freedom to share +and change it. By contrast, the GNU General Public License is intended to +guarantee your freedom to share and change free software--to make sure the +software is free for all its users. This General Public License applies to +most of the Free Software Foundation's software and to any other program whose +authors commit to using it. (Some other Free Software Foundation software is +covered by the GNU Library General Public License instead.) You can apply it to +your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our +General Public Licenses are designed to make sure that you have the freedom to +distribute copies of free software (and charge for this service if you wish), +that you receive source code or can get it if you want it, that you can change +the software or use pieces of it in new free programs; and that you know you +can do these things. + +To protect your rights, we need to make restrictions that forbid anyone to deny +you these rights or to ask you to surrender the rights. These restrictions +translate to certain responsibilities for you if you distribute copies of the +software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or for +a fee, you must give the recipients all the rights that you have. You must +make sure that they, too, receive or can get the source code. And you must +show them these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and (2) +offer you this license which gives you legal permission to copy, distribute +and/or modify the software. + +Also, for each author's protection and ours, we want to make certain that +everyone understands that there is no warranty for this free software. If the +software is modified by someone else and passed on, we want its recipients to +know that what they have is not the original, so that any problems introduced +by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We +wish to avoid the danger that redistributors of a free program will +individually obtain patent licenses, in effect making the program proprietary. +To prevent this, we have made it clear that any patent must be licensed for +everyone's free use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification +follow. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License applies to any program or other work which contains a notice +placed by the copyright holder saying it may be distributed under the terms of +this General Public License. The "Program", below, refers to any such program +or work, and a "work based on the Program" means either the Program or any +derivative work under copyright law: that is to say, a work containing the +Program or a portion of it, either verbatim or with modifications and/or +translated into another language. (Hereinafter, translation is included +without limitation in the term "modification".) Each licensee is addressed as +"you". + +Activities other than copying, distribution and modification are not covered by +this License; they are outside its scope. The act of running the Program is +not restricted, and the output from the Program is covered only if its contents +constitute a work based on the Program (independent of having been made by +running the Program). Whether that is true depends on what the Program does. + +1. You may copy and distribute verbatim copies of the Program's source code as +you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and +disclaimer of warranty; keep intact all the notices that refer to this License +and to the absence of any warranty; and give any other recipients of the +Program a copy of this License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you may +at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, thus +forming a work based on the Program, and copy and distribute such modifications +or work under the terms of Section 1 above, provided that you also meet all of +these conditions: + + a) You must cause the modified files to carry prominent notices stating + that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in whole or + in part contains or is derived from the Program or any part thereof, to be + licensed as a whole at no charge to all third parties under the terms of + this License. + + c) If the modified program normally reads commands interactively when run, + you must cause it, when started running for such interactive use in the + most ordinary way, to print or display an announcement including an + appropriate copyright notice and a notice that there is no warranty (or + else, saying that you provide a warranty) and that users may redistribute + the program under these conditions, and telling the user how to view a copy + of this License. (Exception: if the Program itself is interactive but does + not normally print such an announcement, your work based on the Program is + not required to print an announcement.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Program, and can be reasonably +considered independent and separate works in themselves, then this License, and +its terms, do not apply to those sections when you distribute them as separate +works. But when you distribute the same sections as part of a whole which is a +work based on the Program, the distribution of the whole must be on the terms +of this License, whose permissions for other licensees extend to the entire +whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your +rights to work written entirely by you; rather, the intent is to exercise the +right to control the distribution of derivative or collective works based on +the Program. + +In addition, mere aggregation of another work not based on the Program with the +Program (or with a work based on the Program) on a volume of a storage or +distribution medium does not bring the other work under the scope of this +License. + +3. You may copy and distribute the Program (or a work based on it, under +Section 2) in object code or executable form under the terms of Sections 1 and +2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable source + code, which must be distributed under the terms of Sections 1 and 2 above + on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three years, to + give any third party, for a charge no more than your cost of physically + performing source distribution, a complete machine-readable copy of the + corresponding source code, to be distributed under the terms of Sections 1 + and 2 above on a medium customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer to + distribute corresponding source code. (This alternative is allowed only + for noncommercial distribution and only if you received the program in + object code or executable form with such an offer, in accord with + Subsection b above.) + +The source code for a work means the preferred form of the work for making +modifications to it. For an executable work, complete source code means all +the source code for all modules it contains, plus any associated interface +definition files, plus the scripts used to control compilation and installation +of the executable. However, as a special exception, the source code +distributed need not include anything that is normally distributed (in either +source or binary form) with the major components (compiler, kernel, and so on) +of the operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the source +code from the same place counts as distribution of the source code, even though +third parties are not compelled to copy the source along with the object code. + +4. You may not copy, modify, sublicense, or distribute the Program except as +expressly provided under this License. Any attempt otherwise to copy, modify, +sublicense or distribute the Program is void, and will automatically terminate +your rights under this License. However, parties who have received copies, or +rights, from you under this License will not have their licenses terminated so +long as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not signed it. +However, nothing else grants you permission to modify or distribute the Program +or its derivative works. These actions are prohibited by law if you do not +accept this License. Therefore, by modifying or distributing the Program (or +any work based on the Program), you indicate your acceptance of this License to +do so, and all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the Program), +the recipient automatically receives a license from the original licensor to +copy, distribute or modify the Program subject to these terms and conditions. +You may not impose any further restrictions on the recipients' exercise of the +rights granted herein. You are not responsible for enforcing compliance by +third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), conditions +are imposed on you (whether by court order, agreement or otherwise) that +contradict the conditions of this License, they do not excuse you from the +conditions of this License. If you cannot distribute so as to satisfy +simultaneously your obligations under this License and any other pertinent +obligations, then as a consequence you may not distribute the Program at all. +For example, if a patent license would not permit royalty-free redistribution +of the Program by all those who receive copies directly or indirectly through +you, then the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply and +the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or +other property right claims or to contest validity of any such claims; this +section has the sole purpose of protecting the integrity of the free software +distribution system, which is implemented by public license practices. Many +people have made generous contributions to the wide range of software +distributed through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing to +distribute software through any other system and a licensee cannot impose that +choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain +countries either by patents or by copyrighted interfaces, the original +copyright holder who places the Program under this License may add an explicit +geographical distribution limitation excluding those countries, so that +distribution is permitted only in or among countries not thus excluded. In +such case, this License incorporates the limitation as if written in the body +of this License. + +9. The Free Software Foundation may publish revised and/or new versions of the +General Public License from time to time. Such new versions will be similar in +spirit to the present version, but may differ in detail to address new problems +or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any later +version", you have the option of following the terms and conditions either of +that version or of any later version published by the Free Software Foundation. +If the Program does not specify a version number of this License, you may +choose any version ever published by the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free programs +whose distribution conditions are different, write to the author to ask for +permission. For software which is copyrighted by the Free Software Foundation, +write to the Free Software Foundation; we sometimes make exceptions for this. +Our decision will be guided by the two goals of preserving the free status of +all derivatives of our free software and of promoting the sharing and reuse of +software generally. + +NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR +THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE +STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE +PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND +PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, +YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL +ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE +PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR +INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA +BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER +OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible +use to the public, the best way to achieve this is to make it free software +which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach +them to the start of each source file to most effectively convey the exclusion +of warranty; and each file should have at least the "copyright" line and a +pointer to where the full notice is found. + + One line to give the program's name and a brief idea of what it does. + + Copyright (C) + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this when it +starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author Gnomovision comes + with ABSOLUTELY NO WARRANTY; for details type 'show w'. This is free + software, and you are welcome to redistribute it under certain conditions; + type 'show c' for details. + +The hypothetical commands 'show w' and 'show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may be +called something other than 'show w' and 'show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your school, +if any, to sign a "copyright disclaimer" for the program, if necessary. Here +is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + 'Gnomovision' (which makes passes at compilers) written by James Hacker. + + signature of Ty Coon, 1 April 1989 + + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General Public +License instead of this License. + + +"CLASSPATH" EXCEPTION TO THE GPL + +Certain source files distributed by Oracle America and/or its affiliates are +subject to the following clarification and special exception to the GPL, but +only where Oracle has expressly included in the particular source file's header +the words "Oracle designates this particular file as subject to the "Classpath" +exception as provided by Oracle in the LICENSE file that accompanied this code." + + Linking this library statically or dynamically with other modules is making + a combined work based on this library. Thus, the terms and conditions of + the GNU General Public License cover the whole combination. + + As a special exception, the copyright holders of this library give you + permission to link this library with independent modules to produce an + executable, regardless of the license terms of these independent modules, + and to copy and distribute the resulting executable under terms of your + choice, provided that you also meet, for each linked independent module, + the terms and conditions of the license of that module. An independent + module is a module which is not derived from or based on this library. If + you modify this library, you may extend this exception to your version of + the library, but you are not obligated to do so. If you do not wish to do + so, delete this exception statement from your version. diff --git a/docs/legal/dejavufonts.md b/docs/legal/dejavufonts.md new file mode 100644 index 0000000..8c9e3cf --- /dev/null +++ b/docs/legal/dejavufonts.md @@ -0,0 +1,191 @@ +## DejaVu fonts v2.37 + +### DejaVu License +
+Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
+Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below)
+
+
+Bitstream Vera Fonts Copyright
+------------------------------
+
+Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is
+a trademark of Bitstream, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of the fonts accompanying this license ("Fonts") and associated
+documentation files (the "Font Software"), to reproduce and distribute the
+Font Software, including without limitation the rights to use, copy, merge,
+publish, distribute, and/or sell copies of the Font Software, and to permit
+persons to whom the Font Software is furnished to do so, subject to the
+following conditions:
+
+The above copyright and trademark notices and this permission notice shall
+be included in all copies of one or more of the Font Software typefaces.
+
+The Font Software may be modified, altered, or added to, and in particular
+the designs of glyphs or characters in the Fonts may be modified and
+additional glyphs or characters may be added to the Fonts, only if the fonts
+are renamed to names not containing either the words "Bitstream" or the word
+"Vera".
+
+This License becomes null and void to the extent applicable to Fonts or Font
+Software that has been modified and is distributed under the "Bitstream
+Vera" names.
+
+The Font Software may be sold as part of a larger software package but no
+copy of one or more of the Font Software typefaces may be sold by itself.
+
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
+TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME
+FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING
+ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE
+FONT SOFTWARE.
+
+Except as contained in this notice, the names of Gnome, the Gnome
+Foundation, and Bitstream Inc., shall not be used in advertising or
+otherwise to promote the sale, use or other dealings in this Font Software
+without prior written authorization from the Gnome Foundation or Bitstream
+Inc., respectively. For further information, contact: fonts at gnome dot
+org.
+
+Arev Fonts Copyright
+------------------------------
+
+Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the fonts accompanying this license ("Fonts") and
+associated documentation files (the "Font Software"), to reproduce
+and distribute the modifications to the Bitstream Vera Font Software,
+including without limitation the rights to use, copy, merge, publish,
+distribute, and/or sell copies of the Font Software, and to permit
+persons to whom the Font Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright and trademark notices and this permission notice
+shall be included in all copies of one or more of the Font Software
+typefaces.
+
+The Font Software may be modified, altered, or added to, and in
+particular the designs of glyphs or characters in the Fonts may be
+modified and additional glyphs or characters may be added to the
+Fonts, only if the fonts are renamed to names not containing either
+the words "Tavmjong Bah" or the word "Arev".
+
+This License becomes null and void to the extent applicable to Fonts
+or Font Software that has been modified and is distributed under the
+"Tavmjong Bah Arev" names.
+
+The Font Software may be sold as part of a larger software package but
+no copy of one or more of the Font Software typefaces may be sold by
+itself.
+
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL
+TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
+
+Except as contained in this notice, the name of Tavmjong Bah shall not
+be used in advertising or otherwise to promote the sale, use or other
+dealings in this Font Software without prior written authorization
+from Tavmjong Bah. For further information, contact: tavmjong @ free
+. fr.
+
+TeX Gyre DJV Math
+-----------------
+Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
+
+Math extensions done by B. Jackowski, P. Strzelczyk and P. Pianowski
+(on behalf of TeX users groups) are in public domain.
+
+Letters imported from Euler Fraktur from AMSfonts are (c) American
+Mathematical Society (see below).
+Bitstream Vera Fonts Copyright
+Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera
+is a trademark of Bitstream, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of the fonts accompanying this license ("Fonts") and associated
+documentation
+files (the "Font Software"), to reproduce and distribute the Font Software,
+including without limitation the rights to use, copy, merge, publish,
+distribute,
+and/or sell copies of the Font Software, and to permit persons  to whom
+the Font Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright and trademark notices and this permission notice
+shall be
+included in all copies of one or more of the Font Software typefaces.
+
+The Font Software may be modified, altered, or added to, and in particular
+the designs of glyphs or characters in the Fonts may be modified and
+additional
+glyphs or characters may be added to the Fonts, only if the fonts are
+renamed
+to names not containing either the words "Bitstream" or the word "Vera".
+
+This License becomes null and void to the extent applicable to Fonts or
+Font Software
+that has been modified and is distributed under the "Bitstream Vera"
+names.
+
+The Font Software may be sold as part of a larger software package but
+no copy
+of one or more of the Font Software typefaces may be sold by itself.
+
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
+TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME
+FOUNDATION
+BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL,
+SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN
+ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR
+INABILITY TO USE
+THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
+Except as contained in this notice, the names of GNOME, the GNOME
+Foundation,
+and Bitstream Inc., shall not be used in advertising or otherwise to promote
+the sale, use or other dealings in this Font Software without prior written
+authorization from the GNOME Foundation or Bitstream Inc., respectively.
+For further information, contact: fonts at gnome dot org.
+
+AMSFonts (v. 2.2) copyright
+
+The PostScript Type 1 implementation of the AMSFonts produced by and
+previously distributed by Blue Sky Research and Y&Y, Inc. are now freely
+available for general use. This has been accomplished through the
+cooperation
+of a consortium of scientific publishers with Blue Sky Research and Y&Y.
+Members of this consortium include:
+
+Elsevier Science IBM Corporation Society for Industrial and Applied
+Mathematics (SIAM) Springer-Verlag American Mathematical Society (AMS)
+
+In order to assure the authenticity of these fonts, copyright will be
+held by
+the American Mathematical Society. This is not meant to restrict in any way
+the legitimate use of the fonts, such as (but not limited to) electronic
+distribution of documents containing these fonts, inclusion of these fonts
+into other public domain or commercial font collections or computer
+applications, use of the outline data to create derivative fonts and/or
+faces, etc. However, the AMS does require that the AMS copyright notice be
+removed from any derivative versions of the fonts which have been altered in
+any way. In addition, to ensure the fidelity of TeX documents using Computer
+Modern fonts, Professor Donald Knuth, creator of the Computer Modern faces,
+has requested that any alterations which yield different font metrics be
+given a different name.
+
+
diff --git a/docs/legal/jquery.md b/docs/legal/jquery.md new file mode 100644 index 0000000..a763ec6 --- /dev/null +++ b/docs/legal/jquery.md @@ -0,0 +1,26 @@ +## jQuery v3.7.1 + +### jQuery License +``` +jQuery v 3.7.1 +Copyright OpenJS Foundation and other contributors, https://openjsf.org/ + +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. +``` diff --git a/docs/legal/jqueryUI.md b/docs/legal/jqueryUI.md new file mode 100644 index 0000000..8bda9d7 --- /dev/null +++ b/docs/legal/jqueryUI.md @@ -0,0 +1,49 @@ +## jQuery UI v1.13.2 + +### jQuery UI License +``` +Copyright jQuery Foundation and other contributors, https://jquery.org/ + +This software consists of voluntary contributions made by many +individuals. For exact contribution history, see the revision history +available at https://github.com/jquery/jquery-ui + +The following license applies to all parts of this software except as +documented below: + +==== + +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. + +==== + +Copyright and related rights for sample code are waived via CC0. Sample +code is defined as all source code contained within the demos directory. + +CC0: http://creativecommons.org/publicdomain/zero/1.0/ + +==== + +All files located in the node_modules and external directories are +externally maintained libraries used by this software which have their +own licenses; we recommend you read them, as their terms may differ from +the terms above. + +``` diff --git a/docs/me/mehboss/brewing/BrewAction.html b/docs/me/mehboss/brewing/BrewAction.html new file mode 100644 index 0000000..ff1b7ce --- /dev/null +++ b/docs/me/mehboss/brewing/BrewAction.html @@ -0,0 +1,193 @@ + + + + +BrewAction (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+

Class BrewAction

+
+
java.lang.Object +
me.mehboss.brewing.BrewAction
+
+
+
+
+
public abstract class BrewAction +extends Object
+
Represents a brewing action to perform when brewing completes. + Extend this class to define custom behavior for each brewing recipe.
+
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    Constructors
    +
    +
    Constructor
    +
    Description
    + +
     
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    +
    +
    Modifier and Type
    +
    Method
    +
    Description
    +
    abstract void
    +
    brew(org.bukkit.inventory.BrewerInventory inventory, + org.bukkit.inventory.ItemStack item, + org.bukkit.inventory.ItemStack ingredient)
    +
    +
    Called when brewing completes.
    +
    +
    +
    +
    +
    +

    Methods inherited from class java.lang.Object

    +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      BrewAction

      +
      +
      public BrewAction()
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      brew

      +
      +
      public abstract void brew(org.bukkit.inventory.BrewerInventory inventory, + org.bukkit.inventory.ItemStack item, + org.bukkit.inventory.ItemStack ingredient)
      +
      Called when brewing completes.
      +
      +
      Parameters:
      +
      inventory - the brewing stand inventory
      +
      item - the potion/output slot item
      +
      ingredient - the ingredient used for brewing
      +
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/brewing/BrewEvent.html b/docs/me/mehboss/brewing/BrewEvent.html new file mode 100644 index 0000000..6964a16 --- /dev/null +++ b/docs/me/mehboss/brewing/BrewEvent.html @@ -0,0 +1,153 @@ + + + + +BrewEvent (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+

Class BrewEvent

+
+
java.lang.Object +
me.mehboss.brewing.BrewEvent
+
+
+
+
+
All Implemented Interfaces:
+
org.bukkit.event.Listener
+
+
+
public class BrewEvent +extends Object +implements org.bukkit.event.Listener
+
Handles custom brewing logic: - Allows non-standard items in brewing stands - + Prevents item disappearance with shift-clicks - Automatically starts brewing + when valid custom recipes are present - Supports both empty-slot and + bottle-required brewing
+
+
+
+ +
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      BrewEvent

      +
      +
      public BrewEvent()
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/brewing/Brewery.html b/docs/me/mehboss/brewing/Brewery.html new file mode 100644 index 0000000..2ddc75a --- /dev/null +++ b/docs/me/mehboss/brewing/Brewery.html @@ -0,0 +1,239 @@ + + + + +Brewery (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+

Class Brewery

+
+
java.lang.Object +
me.mehboss.brewing.Brewery
+
+
+
+
+
All Implemented Interfaces:
+
org.bukkit.event.Listener
+
+
+
public class Brewery +extends Object +implements org.bukkit.event.Listener
+
Handles the registration and execution of custom brewing recipes. +

+ This class acts as the core manager for custom brewing logic by: +

    +
  • Maintaining a static singleton reference for recipe access
  • +
  • Listening for brewing stand inventory interactions
  • +
  • Triggering custom brewing behavior when recipe inputs match
  • +
+ Custom brewing recipes are retrieved through BrewingRecipe.getRecipe(BrewerInventory) + and executed using BrewingRecipe.startBrewing(BrewerInventory, BrewingRecipeData).
+
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    Constructors
    +
    +
    Constructor
    +
    Description
    +
    Brewery(org.bukkit.plugin.java.JavaPlugin plugin)
    +
    +
    Creates a new Brewery manager and registers it as an event listener.
    +
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    +
    +
    Modifier and Type
    +
    Method
    +
    Description
    +
    static Brewery
    + +
     
    +
    org.bukkit.plugin.java.JavaPlugin
    + +
     
    +
    void
    +
    onBrewClick(org.bukkit.event.inventory.InventoryClickEvent event)
    +
    +
    Intercepts brewing stand clicks and evaluates whether custom brewing should begin.
    +
    +
    +
    +
    +
    +

    Methods inherited from class java.lang.Object

    +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      Brewery

      +
      +
      public Brewery(org.bukkit.plugin.java.JavaPlugin plugin)
      +
      Creates a new Brewery manager and registers it as an event listener.
      +
      +
      Parameters:
      +
      plugin - The plugin instance that owns this brewing system.
      +
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      getInstance

      +
      +
      public static Brewery getInstance()
      +
      +
      +
    • +
    • +
      +

      getPlugin

      +
      +
      public org.bukkit.plugin.java.JavaPlugin getPlugin()
      +
      +
      +
    • +
    • +
      +

      onBrewClick

      +
      +
      public void onBrewClick(org.bukkit.event.inventory.InventoryClickEvent event)
      +
      Intercepts brewing stand clicks and evaluates whether custom brewing should begin. +

      + This event handler: +

      +
      +
      Parameters:
      +
      event - The InventoryClickEvent triggered when a player interacts with an inventory.
      +
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/brewing/BrewingRecipe.html b/docs/me/mehboss/brewing/BrewingRecipe.html new file mode 100644 index 0000000..210621f --- /dev/null +++ b/docs/me/mehboss/brewing/BrewingRecipe.html @@ -0,0 +1,240 @@ + + + + +BrewingRecipe (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+

Class BrewingRecipe

+
+
java.lang.Object +
me.mehboss.brewing.BrewingRecipe
+
+
+
+
+
public class BrewingRecipe +extends Object
+
Handles runtime logic for custom brewing recipes. +

+ This class: +

    +
  • Locates matching brewing recipes based on inventory contents
  • +
  • Starts and manages brewing timers
  • +
  • Consumes fuel and ingredients
  • +
  • Controls progress via NBT BrewTime
  • +
  • Applies custom brewing results
  • +
+ +

Note: This class does not store recipe definitions. All + recipe data is stored within BrewingRecipeData via RecipeUtil.

+
+
+
+ +
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      BrewingRecipe

      +
      +
      public BrewingRecipe()
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      getRecipe

      +
      +
      public static BrewingRecipeData getRecipe(org.bukkit.inventory.BrewerInventory inventory)
      +
      Attempts to locate a brewing recipe that matches the given brewer inventory. +

      + Matching criteria: +

        +
      • Ingredient slot item matches recipe ingredient
      • +
      • Fuel slot item matches recipe fuel
      • +
      • Recipe is active
      • +
      • Player has permission (if required)
      • +
      • World is not disabled
      • +
      +
      +
      Parameters:
      +
      inventory - The brewing stand inventory to match.
      +
      Returns:
      +
      A matching BrewingRecipeData, or null if none match.
      +
      +
      +
      +
    • +
    • +
      +

      startBrewing

      +
      +
      public static void startBrewing(org.bukkit.inventory.BrewerInventory inventory, + BrewingRecipeData recipe)
      +
      Begins a brewing cycle using the provided recipe. +

      + The default brewing time is 400 ticks (20 seconds), unless overridden in + BrewingRecipe.BrewClock. + +

      This method: +

        +
      • Consumes fuel if internal fuel tank is empty
      • +
      • Initializes a BrewingRecipe.BrewClock to track progress
      • +
      +
      +
      Parameters:
      +
      inventory - Brewer inventory involved in brewing.
      +
      recipe - The recipe to brew.
      +
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/brewing/class-use/BrewAction.html b/docs/me/mehboss/brewing/class-use/BrewAction.html new file mode 100644 index 0000000..4119b75 --- /dev/null +++ b/docs/me/mehboss/brewing/class-use/BrewAction.html @@ -0,0 +1,109 @@ + + + + +Uses of Class me.mehboss.brewing.BrewAction (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Class
me.mehboss.brewing.BrewAction

+
+
Packages that use BrewAction
+
+
Package
+
Description
+ +
+
Contains data models for defining custom recipe behavior.
+
+
+
+ +
+
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/brewing/class-use/BrewEvent.html b/docs/me/mehboss/brewing/class-use/BrewEvent.html new file mode 100644 index 0000000..8ca5f98 --- /dev/null +++ b/docs/me/mehboss/brewing/class-use/BrewEvent.html @@ -0,0 +1,70 @@ + + + + +Uses of Class me.mehboss.brewing.BrewEvent (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Class
me.mehboss.brewing.BrewEvent

+
+No usage of me.mehboss.brewing.BrewEvent +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/brewing/class-use/Brewery.html b/docs/me/mehboss/brewing/class-use/Brewery.html new file mode 100644 index 0000000..d1c1582 --- /dev/null +++ b/docs/me/mehboss/brewing/class-use/Brewery.html @@ -0,0 +1,96 @@ + + + + +Uses of Class me.mehboss.brewing.Brewery (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Class
me.mehboss.brewing.Brewery

+
+
Packages that use Brewery
+
+
Package
+
Description
+ +
+
Handles custom brewing stand recipes and brewing mechanics.
+
+
+
+ +
+
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/brewing/class-use/BrewingRecipe.html b/docs/me/mehboss/brewing/class-use/BrewingRecipe.html new file mode 100644 index 0000000..8b24c82 --- /dev/null +++ b/docs/me/mehboss/brewing/class-use/BrewingRecipe.html @@ -0,0 +1,70 @@ + + + + +Uses of Class me.mehboss.brewing.BrewingRecipe (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Class
me.mehboss.brewing.BrewingRecipe

+
+No usage of me.mehboss.brewing.BrewingRecipe +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/brewing/package-summary.html b/docs/me/mehboss/brewing/package-summary.html new file mode 100644 index 0000000..bec79a0 --- /dev/null +++ b/docs/me/mehboss/brewing/package-summary.html @@ -0,0 +1,118 @@ + + + + +me.mehboss.brewing (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+
+

Package me.mehboss.brewing

+
+
+
+
package me.mehboss.brewing
+
+
Handles custom brewing stand recipes and brewing mechanics. +

+ Includes recipe matching, fuel handling, brewing timers, and result + processing through the BrewingRecipe and BrewClock systems.

+
+
+
+
    +
  • +
    +
    Classes
    +
    +
    Class
    +
    Description
    + +
    +
    Represents a brewing action to perform when brewing completes.
    +
    + +
    +
    Handles the registration and execution of custom brewing recipes.
    +
    + +
    +
    Handles custom brewing logic: - Allows non-standard items in brewing stands - + Prevents item disappearance with shift-clicks - Automatically starts brewing + when valid custom recipes are present - Supports both empty-slot and + bottle-required brewing
    +
    + +
    +
    Handles runtime logic for custom brewing recipes.
    +
    +
    +
    +
  • +
+
+
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/brewing/package-tree.html b/docs/me/mehboss/brewing/package-tree.html new file mode 100644 index 0000000..3281013 --- /dev/null +++ b/docs/me/mehboss/brewing/package-tree.html @@ -0,0 +1,85 @@ + + + + +me.mehboss.brewing Class Hierarchy (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Hierarchy For Package me.mehboss.brewing

+
+Package Hierarchies: + +
+

Class Hierarchy

+
    +
  • java.lang.Object +
      +
    • me.mehboss.brewing.BrewAction
    • +
    • me.mehboss.brewing.Brewery (implements org.bukkit.event.Listener)
    • +
    • me.mehboss.brewing.BrewEvent (implements org.bukkit.event.Listener)
    • +
    • me.mehboss.brewing.BrewingRecipe
    • +
    +
  • +
+
+
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/brewing/package-use.html b/docs/me/mehboss/brewing/package-use.html new file mode 100644 index 0000000..eaabe13 --- /dev/null +++ b/docs/me/mehboss/brewing/package-use.html @@ -0,0 +1,111 @@ + + + + +Uses of Package me.mehboss.brewing (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Package
me.mehboss.brewing

+
+
Packages that use me.mehboss.brewing
+
+
Package
+
Description
+ +
+
Handles custom brewing stand recipes and brewing mechanics.
+
+ +
+
Contains data models for defining custom recipe behavior.
+
+
+
+ +
+
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/commands/CRCommand.html b/docs/me/mehboss/commands/CRCommand.html new file mode 100644 index 0000000..7b35673 --- /dev/null +++ b/docs/me/mehboss/commands/CRCommand.html @@ -0,0 +1,223 @@ + + + + +CRCommand (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+

Class CRCommand

+
+
java.lang.Object +
me.mehboss.commands.CRCommand
+
+
+
+
+
public class CRCommand +extends Object
+
+
+
+ +
+
+
    + +
  • +
    +

    Field Details

    +
      +
    • +
      +

      sender

      +
      +
      public final org.bukkit.command.CommandSender sender
      +
      +
      +
    • +
    • +
      +

      command

      +
      +
      public final org.bukkit.command.Command command
      +
      +
      +
    • +
    • +
      +

      label

      +
      +
      public final String label
      +
      +
      +
    • +
    • +
      +

      args

      +
      +
      public final String[] args
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      CRCommand

      +
      +
      public CRCommand(org.bukkit.command.CommandSender sender, + org.bukkit.command.Command command, + String label, + String[] args)
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/commands/CommandBook.html b/docs/me/mehboss/commands/CommandBook.html new file mode 100644 index 0000000..b93c29c --- /dev/null +++ b/docs/me/mehboss/commands/CommandBook.html @@ -0,0 +1,178 @@ + + + + +CommandBook (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+

Class CommandBook

+
+
java.lang.Object +
me.mehboss.commands.CommandBook
+
+
+
+
+
public class CommandBook +extends Object
+
+
+
+ +
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      CommandBook

      +
      +
      public CommandBook()
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      Run

      +
      +
      public static boolean Run(CRCommand command)
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/commands/CommandCrafterDebug.html b/docs/me/mehboss/commands/CommandCrafterDebug.html new file mode 100644 index 0000000..5bf4e25 --- /dev/null +++ b/docs/me/mehboss/commands/CommandCrafterDebug.html @@ -0,0 +1,178 @@ + + + + +CommandCrafterDebug (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+

Class CommandCrafterDebug

+
+
java.lang.Object +
me.mehboss.commands.CommandCrafterDebug
+
+
+
+
+
public class CommandCrafterDebug +extends Object
+
+
+
+ +
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      CommandCrafterDebug

      +
      +
      public CommandCrafterDebug()
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      Run

      +
      +
      public static boolean Run(CRCommand command)
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/commands/CommandDebug.html b/docs/me/mehboss/commands/CommandDebug.html new file mode 100644 index 0000000..0771c25 --- /dev/null +++ b/docs/me/mehboss/commands/CommandDebug.html @@ -0,0 +1,178 @@ + + + + +CommandDebug (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+

Class CommandDebug

+
+
java.lang.Object +
me.mehboss.commands.CommandDebug
+
+
+
+
+
public class CommandDebug +extends Object
+
+
+
+ +
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      CommandDebug

      +
      +
      public CommandDebug()
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      Run

      +
      +
      public static boolean Run(CRCommand command)
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/commands/CommandEditItem.html b/docs/me/mehboss/commands/CommandEditItem.html new file mode 100644 index 0000000..8bc70e2 --- /dev/null +++ b/docs/me/mehboss/commands/CommandEditItem.html @@ -0,0 +1,178 @@ + + + + +CommandEditItem (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+

Class CommandEditItem

+
+
java.lang.Object +
me.mehboss.commands.CommandEditItem
+
+
+
+
+
public class CommandEditItem +extends Object
+
+
+
+ +
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      CommandEditItem

      +
      +
      public CommandEditItem()
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      Run

      +
      +
      public static boolean Run(CRCommand command)
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/commands/CommandGUI.html b/docs/me/mehboss/commands/CommandGUI.html new file mode 100644 index 0000000..031922c --- /dev/null +++ b/docs/me/mehboss/commands/CommandGUI.html @@ -0,0 +1,178 @@ + + + + +CommandGUI (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+

Class CommandGUI

+
+
java.lang.Object +
me.mehboss.commands.CommandGUI
+
+
+
+
+
public class CommandGUI +extends Object
+
+
+
+ +
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      CommandGUI

      +
      +
      public CommandGUI()
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      Run

      +
      +
      public static boolean Run(CRCommand command)
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/commands/CommandGive.html b/docs/me/mehboss/commands/CommandGive.html new file mode 100644 index 0000000..930a595 --- /dev/null +++ b/docs/me/mehboss/commands/CommandGive.html @@ -0,0 +1,190 @@ + + + + +CommandGive (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+

Class CommandGive

+
+
java.lang.Object +
me.mehboss.commands.CommandGive
+
+
+
+
+
public class CommandGive +extends Object
+
+
+
+ +
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      CommandGive

      +
      +
      public CommandGive()
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      Run

      +
      +
      public static boolean Run(CRCommand command)
      +
      +
      +
    • +
    • +
      +

      isInt

      +
      +
      public static boolean isInt(String text)
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/commands/CommandHelp.html b/docs/me/mehboss/commands/CommandHelp.html new file mode 100644 index 0000000..a9bd5da --- /dev/null +++ b/docs/me/mehboss/commands/CommandHelp.html @@ -0,0 +1,178 @@ + + + + +CommandHelp (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+

Class CommandHelp

+
+
java.lang.Object +
me.mehboss.commands.CommandHelp
+
+
+
+
+
public class CommandHelp +extends Object
+
+
+
+ +
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      CommandHelp

      +
      +
      public CommandHelp()
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      Run

      +
      +
      public static boolean Run(CRCommand command)
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/commands/CommandItems.html b/docs/me/mehboss/commands/CommandItems.html new file mode 100644 index 0000000..a6bb26c --- /dev/null +++ b/docs/me/mehboss/commands/CommandItems.html @@ -0,0 +1,178 @@ + + + + +CommandItems (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+

Class CommandItems

+
+
java.lang.Object +
me.mehboss.commands.CommandItems
+
+
+
+
+
public class CommandItems +extends Object
+
+
+
+ +
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      CommandItems

      +
      +
      public CommandItems()
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      Run

      +
      +
      public static boolean Run(CRCommand command)
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/commands/CommandList.html b/docs/me/mehboss/commands/CommandList.html new file mode 100644 index 0000000..6b51a6a --- /dev/null +++ b/docs/me/mehboss/commands/CommandList.html @@ -0,0 +1,178 @@ + + + + +CommandList (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+

Class CommandList

+
+
java.lang.Object +
me.mehboss.commands.CommandList
+
+
+
+
+
public class CommandList +extends Object
+
+
+
+ +
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      CommandList

      +
      +
      public CommandList()
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      Run

      +
      +
      public static boolean Run(CRCommand command)
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/commands/CommandRecipe.html b/docs/me/mehboss/commands/CommandRecipe.html new file mode 100644 index 0000000..4194e2b --- /dev/null +++ b/docs/me/mehboss/commands/CommandRecipe.html @@ -0,0 +1,178 @@ + + + + +CommandRecipe (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+

Class CommandRecipe

+
+
java.lang.Object +
me.mehboss.commands.CommandRecipe
+
+
+
+
+
public class CommandRecipe +extends Object
+
+
+
+ +
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      CommandRecipe

      +
      +
      public CommandRecipe()
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      Run

      +
      +
      public static boolean Run(CRCommand command)
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/commands/CommandReload.html b/docs/me/mehboss/commands/CommandReload.html new file mode 100644 index 0000000..8795a79 --- /dev/null +++ b/docs/me/mehboss/commands/CommandReload.html @@ -0,0 +1,178 @@ + + + + +CommandReload (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+

Class CommandReload

+
+
java.lang.Object +
me.mehboss.commands.CommandReload
+
+
+
+
+
public class CommandReload +extends Object
+
+
+
+ +
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      CommandReload

      +
      +
      public CommandReload()
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      Run

      +
      +
      public static boolean Run(CRCommand command)
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/commands/CommandRemove.html b/docs/me/mehboss/commands/CommandRemove.html new file mode 100644 index 0000000..a4f6b94 --- /dev/null +++ b/docs/me/mehboss/commands/CommandRemove.html @@ -0,0 +1,190 @@ + + + + +CommandRemove (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+

Class CommandRemove

+
+
java.lang.Object +
me.mehboss.commands.CommandRemove
+
+
+
+
+
public class CommandRemove +extends Object
+
+
+
+ +
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      CommandRemove

      +
      +
      public CommandRemove()
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      Run

      +
      +
      public static boolean Run(CRCommand command)
      +
      +
      +
    • +
    • +
      +

      removeRecipe

      +
      +
      public static void removeRecipe(String name)
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/commands/CommandShow.html b/docs/me/mehboss/commands/CommandShow.html new file mode 100644 index 0000000..3c58c4a --- /dev/null +++ b/docs/me/mehboss/commands/CommandShow.html @@ -0,0 +1,178 @@ + + + + +CommandShow (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+

Class CommandShow

+
+
java.lang.Object +
me.mehboss.commands.CommandShow
+
+
+
+
+
public class CommandShow +extends Object
+
+
+
+ +
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      CommandShow

      +
      +
      public CommandShow()
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      Run

      +
      +
      public static boolean Run(CRCommand command)
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/commands/TabCompletion.html b/docs/me/mehboss/commands/TabCompletion.html new file mode 100644 index 0000000..07a62a8 --- /dev/null +++ b/docs/me/mehboss/commands/TabCompletion.html @@ -0,0 +1,193 @@ + + + + +TabCompletion (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+

Class TabCompletion

+
+
java.lang.Object +
me.mehboss.commands.TabCompletion
+
+
+
+
+
All Implemented Interfaces:
+
org.bukkit.command.TabCompleter
+
+
+
public class TabCompletion +extends Object +implements org.bukkit.command.TabCompleter
+
+
+
+ +
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      TabCompletion

      +
      +
      public TabCompletion()
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      onTabComplete

      +
      +
      public List<String> onTabComplete(org.bukkit.command.CommandSender sender, + org.bukkit.command.Command command, + String alias, + String[] args)
      +
      +
      Specified by:
      +
      onTabComplete in interface org.bukkit.command.TabCompleter
      +
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/commands/class-use/CRCommand.html b/docs/me/mehboss/commands/class-use/CRCommand.html new file mode 100644 index 0000000..deed250 --- /dev/null +++ b/docs/me/mehboss/commands/class-use/CRCommand.html @@ -0,0 +1,133 @@ + + + + +Uses of Class me.mehboss.commands.CRCommand (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Class
me.mehboss.commands.CRCommand

+
+
Packages that use CRCommand
+
+
Package
+
Description
+ +
+
Provides all command handlers for the plugin, including administrative + commands, player utilities, debug tools, and recipe management commands.
+
+
+
+ +
+
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/commands/class-use/CommandBook.html b/docs/me/mehboss/commands/class-use/CommandBook.html new file mode 100644 index 0000000..ce54e40 --- /dev/null +++ b/docs/me/mehboss/commands/class-use/CommandBook.html @@ -0,0 +1,70 @@ + + + + +Uses of Class me.mehboss.commands.CommandBook (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Class
me.mehboss.commands.CommandBook

+
+No usage of me.mehboss.commands.CommandBook +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/commands/class-use/CommandCrafterDebug.html b/docs/me/mehboss/commands/class-use/CommandCrafterDebug.html new file mode 100644 index 0000000..97bf828 --- /dev/null +++ b/docs/me/mehboss/commands/class-use/CommandCrafterDebug.html @@ -0,0 +1,70 @@ + + + + +Uses of Class me.mehboss.commands.CommandCrafterDebug (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Class
me.mehboss.commands.CommandCrafterDebug

+
+No usage of me.mehboss.commands.CommandCrafterDebug +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/commands/class-use/CommandDebug.html b/docs/me/mehboss/commands/class-use/CommandDebug.html new file mode 100644 index 0000000..1c71d47 --- /dev/null +++ b/docs/me/mehboss/commands/class-use/CommandDebug.html @@ -0,0 +1,70 @@ + + + + +Uses of Class me.mehboss.commands.CommandDebug (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Class
me.mehboss.commands.CommandDebug

+
+No usage of me.mehboss.commands.CommandDebug +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/commands/class-use/CommandEditItem.html b/docs/me/mehboss/commands/class-use/CommandEditItem.html new file mode 100644 index 0000000..19094a0 --- /dev/null +++ b/docs/me/mehboss/commands/class-use/CommandEditItem.html @@ -0,0 +1,70 @@ + + + + +Uses of Class me.mehboss.commands.CommandEditItem (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Class
me.mehboss.commands.CommandEditItem

+
+No usage of me.mehboss.commands.CommandEditItem +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/commands/class-use/CommandGUI.html b/docs/me/mehboss/commands/class-use/CommandGUI.html new file mode 100644 index 0000000..9174053 --- /dev/null +++ b/docs/me/mehboss/commands/class-use/CommandGUI.html @@ -0,0 +1,70 @@ + + + + +Uses of Class me.mehboss.commands.CommandGUI (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Class
me.mehboss.commands.CommandGUI

+
+No usage of me.mehboss.commands.CommandGUI +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/commands/class-use/CommandGive.html b/docs/me/mehboss/commands/class-use/CommandGive.html new file mode 100644 index 0000000..8a7d263 --- /dev/null +++ b/docs/me/mehboss/commands/class-use/CommandGive.html @@ -0,0 +1,70 @@ + + + + +Uses of Class me.mehboss.commands.CommandGive (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Class
me.mehboss.commands.CommandGive

+
+No usage of me.mehboss.commands.CommandGive +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/commands/class-use/CommandHelp.html b/docs/me/mehboss/commands/class-use/CommandHelp.html new file mode 100644 index 0000000..202e630 --- /dev/null +++ b/docs/me/mehboss/commands/class-use/CommandHelp.html @@ -0,0 +1,70 @@ + + + + +Uses of Class me.mehboss.commands.CommandHelp (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Class
me.mehboss.commands.CommandHelp

+
+No usage of me.mehboss.commands.CommandHelp +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/commands/class-use/CommandItems.html b/docs/me/mehboss/commands/class-use/CommandItems.html new file mode 100644 index 0000000..96b4ee1 --- /dev/null +++ b/docs/me/mehboss/commands/class-use/CommandItems.html @@ -0,0 +1,70 @@ + + + + +Uses of Class me.mehboss.commands.CommandItems (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Class
me.mehboss.commands.CommandItems

+
+No usage of me.mehboss.commands.CommandItems +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/commands/class-use/CommandList.html b/docs/me/mehboss/commands/class-use/CommandList.html new file mode 100644 index 0000000..b00d1e5 --- /dev/null +++ b/docs/me/mehboss/commands/class-use/CommandList.html @@ -0,0 +1,70 @@ + + + + +Uses of Class me.mehboss.commands.CommandList (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Class
me.mehboss.commands.CommandList

+
+No usage of me.mehboss.commands.CommandList +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/commands/class-use/CommandRecipe.html b/docs/me/mehboss/commands/class-use/CommandRecipe.html new file mode 100644 index 0000000..21c5f38 --- /dev/null +++ b/docs/me/mehboss/commands/class-use/CommandRecipe.html @@ -0,0 +1,70 @@ + + + + +Uses of Class me.mehboss.commands.CommandRecipe (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Class
me.mehboss.commands.CommandRecipe

+
+No usage of me.mehboss.commands.CommandRecipe +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/commands/class-use/CommandReload.html b/docs/me/mehboss/commands/class-use/CommandReload.html new file mode 100644 index 0000000..b5f23a7 --- /dev/null +++ b/docs/me/mehboss/commands/class-use/CommandReload.html @@ -0,0 +1,70 @@ + + + + +Uses of Class me.mehboss.commands.CommandReload (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Class
me.mehboss.commands.CommandReload

+
+No usage of me.mehboss.commands.CommandReload +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/commands/class-use/CommandRemove.html b/docs/me/mehboss/commands/class-use/CommandRemove.html new file mode 100644 index 0000000..c81f768 --- /dev/null +++ b/docs/me/mehboss/commands/class-use/CommandRemove.html @@ -0,0 +1,70 @@ + + + + +Uses of Class me.mehboss.commands.CommandRemove (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Class
me.mehboss.commands.CommandRemove

+
+No usage of me.mehboss.commands.CommandRemove +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/commands/class-use/CommandShow.html b/docs/me/mehboss/commands/class-use/CommandShow.html new file mode 100644 index 0000000..0b88f71 --- /dev/null +++ b/docs/me/mehboss/commands/class-use/CommandShow.html @@ -0,0 +1,70 @@ + + + + +Uses of Class me.mehboss.commands.CommandShow (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Class
me.mehboss.commands.CommandShow

+
+No usage of me.mehboss.commands.CommandShow +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/commands/class-use/TabCompletion.html b/docs/me/mehboss/commands/class-use/TabCompletion.html new file mode 100644 index 0000000..f11ad3c --- /dev/null +++ b/docs/me/mehboss/commands/class-use/TabCompletion.html @@ -0,0 +1,70 @@ + + + + +Uses of Class me.mehboss.commands.TabCompletion (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Class
me.mehboss.commands.TabCompletion

+
+No usage of me.mehboss.commands.TabCompletion +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/commands/package-summary.html b/docs/me/mehboss/commands/package-summary.html new file mode 100644 index 0000000..706ce91 --- /dev/null +++ b/docs/me/mehboss/commands/package-summary.html @@ -0,0 +1,130 @@ + + + + +me.mehboss.commands (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+
+

Package me.mehboss.commands

+
+
+
+
package me.mehboss.commands
+
+
Provides all command handlers for the plugin, including administrative + commands, player utilities, debug tools, and recipe management commands. +

+ Each command class implements a specific action such as listing recipes, + reloading configuration, or giving custom items.

+
+
+
+ +
+
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/commands/package-tree.html b/docs/me/mehboss/commands/package-tree.html new file mode 100644 index 0000000..1b4541d --- /dev/null +++ b/docs/me/mehboss/commands/package-tree.html @@ -0,0 +1,96 @@ + + + + +me.mehboss.commands Class Hierarchy (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Hierarchy For Package me.mehboss.commands

+
+Package Hierarchies: + +
+

Class Hierarchy

+ +
+
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/commands/package-use.html b/docs/me/mehboss/commands/package-use.html new file mode 100644 index 0000000..db10ec3 --- /dev/null +++ b/docs/me/mehboss/commands/package-use.html @@ -0,0 +1,93 @@ + + + + +Uses of Package me.mehboss.commands (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Package
me.mehboss.commands

+
+
Packages that use me.mehboss.commands
+
+
Package
+
Description
+ +
+
Provides all command handlers for the plugin, including administrative + commands, player utilities, debug tools, and recipe management commands.
+
+
+
+ +
+
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/recipe/CommandListener.html b/docs/me/mehboss/recipe/CommandListener.html new file mode 100644 index 0000000..c4de218 --- /dev/null +++ b/docs/me/mehboss/recipe/CommandListener.html @@ -0,0 +1,193 @@ + + + + +CommandListener (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+

Class CommandListener

+
+
java.lang.Object +
me.mehboss.recipe.CommandListener
+
+
+
+
+
All Implemented Interfaces:
+
org.bukkit.command.CommandExecutor
+
+
+
public class CommandListener +extends Object +implements org.bukkit.command.CommandExecutor
+
+
+
+ +
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      CommandListener

      +
      +
      public CommandListener()
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      onCommand

      +
      +
      public boolean onCommand(org.bukkit.command.CommandSender sender, + org.bukkit.command.Command command, + String label, + String[] args)
      +
      +
      Specified by:
      +
      onCommand in interface org.bukkit.command.CommandExecutor
      +
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/recipe/Main.html b/docs/me/mehboss/recipe/Main.html new file mode 100644 index 0000000..68765ea --- /dev/null +++ b/docs/me/mehboss/recipe/Main.html @@ -0,0 +1,412 @@ + + + + +Main (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+

Class Main

+
+
java.lang.Object +
org.bukkit.plugin.PluginBase +
org.bukkit.plugin.java.JavaPlugin +
me.mehboss.recipe.Main
+
+
+
+
+
+
+
All Implemented Interfaces:
+
org.bukkit.command.CommandExecutor, org.bukkit.command.TabCompleter, org.bukkit.command.TabExecutor, org.bukkit.event.Listener, org.bukkit.plugin.Plugin
+
+
+
public class Main +extends org.bukkit.plugin.java.JavaPlugin +implements org.bukkit.event.Listener
+
+
+
+ +
+
+
    + +
  • +
    +

    Field Details

    +
      +
    • +
      +

      cooldownManager

      +
      +
      public CooldownManager cooldownManager
      +
      +
      +
    • +
    • +
      +

      recipeUtil

      +
      +
      public RecipeUtil recipeUtil
      +
      +
      +
    • +
    • +
      +

      debounceMap

      +
      +
      public Map<UUID,Long> debounceMap
      +
      +
      +
    • +
    • +
      +

      inInventory

      +
      +
      public ArrayList<UUID> inInventory
      +
      +
      +
    • +
    • +
      +

      recipeBook

      +
      +
      public ArrayList<UUID> recipeBook
      +
      +
      +
    • +
    • +
      +

      saveInventory

      +
      +
      public HashMap<UUID,org.bukkit.inventory.Inventory> saveInventory
      +
      +
      +
    • +
    • +
      +

      disabledrecipe

      +
      +
      public ArrayList<String> disabledrecipe
      +
      +
      +
    • +
    • +
      +

      customConfig

      +
      +
      public org.bukkit.configuration.file.FileConfiguration customConfig
      +
      +
      +
    • +
    • +
      +

      hasAE

      +
      +
      public Boolean hasAE
      +
      +
      +
    • +
    • +
      +

      hasEE

      +
      +
      public Boolean hasEE
      +
      +
      +
    • +
    • +
      +

      hasHavenBags

      +
      +
      public Boolean hasHavenBags
      +
      +
      +
    • +
    • +
      +

      debug

      +
      +
      public Boolean debug
      +
      +
      +
    • +
    • +
      +

      crafterdebug

      +
      +
      public Boolean crafterdebug
      +
      +
      +
    • +
    • +
      +

      metaChecks

      +
      +
      public MetaChecks metaChecks
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      Main

      +
      +
      public Main()
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      getRecipeUtil

      +
      +
      public RecipeUtil getRecipeUtil()
      +
      +
      +
    • +
    • +
      +

      getInstance

      +
      +
      public static Main getInstance()
      +
      +
      +
    • +
    • +
      +

      serverVersionAtLeast

      +
      +
      public boolean serverVersionAtLeast(int major, + int minor)
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/recipe/class-use/CommandListener.html b/docs/me/mehboss/recipe/class-use/CommandListener.html new file mode 100644 index 0000000..4a8eb23 --- /dev/null +++ b/docs/me/mehboss/recipe/class-use/CommandListener.html @@ -0,0 +1,70 @@ + + + + +Uses of Class me.mehboss.recipe.CommandListener (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Class
me.mehboss.recipe.CommandListener

+
+No usage of me.mehboss.recipe.CommandListener +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/recipe/class-use/Main.html b/docs/me/mehboss/recipe/class-use/Main.html new file mode 100644 index 0000000..a25d2c8 --- /dev/null +++ b/docs/me/mehboss/recipe/class-use/Main.html @@ -0,0 +1,96 @@ + + + + +Uses of Class me.mehboss.recipe.Main (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Class
me.mehboss.recipe.Main

+
+
Packages that use Main
+
+
Package
+
Description
+ +
+
Core recipe management system.
+
+
+
+ +
+
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/recipe/package-summary.html b/docs/me/mehboss/recipe/package-summary.html new file mode 100644 index 0000000..26ab3b1 --- /dev/null +++ b/docs/me/mehboss/recipe/package-summary.html @@ -0,0 +1,104 @@ + + + + +me.mehboss.recipe (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+
+

Package me.mehboss.recipe

+
+
+
+
package me.mehboss.recipe
+
+
Core recipe management system. +

+ Contains the main plugin initialization, recipe storage, loading, + registration, and the central RecipeUtil class used throughout + the plugin to resolve ingredients and results.

+
+
+
+ +
+
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/recipe/package-tree.html b/docs/me/mehboss/recipe/package-tree.html new file mode 100644 index 0000000..c4ca908 --- /dev/null +++ b/docs/me/mehboss/recipe/package-tree.html @@ -0,0 +1,91 @@ + + + + +me.mehboss.recipe Class Hierarchy (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Hierarchy For Package me.mehboss.recipe

+
+Package Hierarchies: + +
+

Class Hierarchy

+
    +
  • java.lang.Object +
      +
    • me.mehboss.recipe.CommandListener (implements org.bukkit.command.CommandExecutor)
    • +
    • org.bukkit.plugin.PluginBase (implements org.bukkit.plugin.Plugin) +
        +
      • org.bukkit.plugin.java.JavaPlugin +
          +
        • me.mehboss.recipe.Main (implements org.bukkit.event.Listener)
        • +
        +
      • +
      +
    • +
    +
  • +
+
+
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/recipe/package-use.html b/docs/me/mehboss/recipe/package-use.html new file mode 100644 index 0000000..f1788e7 --- /dev/null +++ b/docs/me/mehboss/recipe/package-use.html @@ -0,0 +1,92 @@ + + + + +Uses of Package me.mehboss.recipe (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Package
me.mehboss.recipe

+
+
Packages that use me.mehboss.recipe
+
+
Package
+
Description
+ +
+
Core recipe management system.
+
+
+
+ +
+
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/CompatibilityUtil.html b/docs/me/mehboss/utils/CompatibilityUtil.html new file mode 100644 index 0000000..e2d16a6 --- /dev/null +++ b/docs/me/mehboss/utils/CompatibilityUtil.html @@ -0,0 +1,408 @@ + + + + +CompatibilityUtil (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+

Class CompatibilityUtil

+
+
java.lang.Object +
me.mehboss.utils.CompatibilityUtil
+
+
+
+
+
public class CompatibilityUtil +extends Object
+
Utility class providing cross-version compatibility helpers that rely on + reflection to handle differences between Minecraft / Bukkit versions. +

+ This includes: +

    +
  • Accessing InventoryView methods safely across versions
  • +
  • Retrieving players, titles, and top inventories without relying on version-specific classes
  • +
  • Reflection helpers for invoking private methods and fields
  • +
  • Base64 decoding for skin texture analysis
  • +
  • Item name getters/setters supporting 1.20.5+ name changes
  • +
+
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    Constructors
    +
    +
    Constructor
    +
    Description
    + +
     
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    +
    +
    Modifier and Type
    +
    Method
    +
    Description
    +
    static String
    + +
    +
    Extracts the skin texture URL from a Base64-encoded JSON texture payload.
    +
    +
    static String
    +
    getDisplayname(org.bukkit.inventory.meta.ItemMeta item)
    +
    +
    Retrieves the display name from an item.
    +
    +
    static Object
    +
    getInventoryView(org.bukkit.event.inventory.InventoryEvent event)
    +
    +
    Retrieves the InventoryView from an InventoryEvent using reflection.
    +
    +
    static Method
    +
    getMethod(Class<?> clazz, + String name, + Class<?>... parameterTypes)
    +
     
    +
    static org.bukkit.entity.Player
    + +
    +
    Extracts the Player from an InventoryView instance using reflection.
    +
    +
    static String
    +
    getTitle(org.bukkit.event.inventory.InventoryEvent event)
    +
    +
    Retrieves an inventory's title reflectively.
    +
    +
    static org.bukkit.inventory.Inventory
    +
    getTopInventory(org.bukkit.event.inventory.InventoryEvent event)
    +
    +
    Retrieves the top inventory from an InventoryEvent using reflection.
    +
    +
    static boolean
    +
    hasDisplayname(org.bukkit.inventory.meta.ItemMeta item)
    +
    +
    Checks if an item has a display name.
    +
    +
    static void
    +
    invokeMethod(Method method, + Object instance, + Object... args)
    +
     
    +
    static org.bukkit.inventory.meta.ItemMeta
    +
    setDisplayname(org.bukkit.inventory.ItemStack item, + String name)
    +
    +
    Sets an item's display name using version-aware APIs.
    +
    +
    static void
    +
    setFieldValue(Object instance, + String fieldName, + Object value)
    +
     
    +
    +
    +
    +
    +

    Methods inherited from class java.lang.Object

    +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      CompatibilityUtil

      +
      +
      public CompatibilityUtil()
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      getInventoryView

      +
      +
      public static Object getInventoryView(org.bukkit.event.inventory.InventoryEvent event)
      +
      Retrieves the InventoryView from an InventoryEvent using reflection. +

      + Different server versions may change how InventoryView is exposed, + so this reflective access ensures compatibility.

      +
      +
      Parameters:
      +
      event - The inventory event.
      +
      Returns:
      +
      The InventoryView object (returned as Object for compatibility).
      +
      +
      +
      +
    • +
    • +
      +

      getPlayerFromView

      +
      +
      public static org.bukkit.entity.Player getPlayerFromView(Object view)
      +
      Extracts the Player from an InventoryView instance using reflection.
      +
      +
      Parameters:
      +
      view - The InventoryView object.
      +
      Returns:
      +
      The player who opened the inventory, or null if unavailable.
      +
      +
      +
      +
    • +
    • +
      +

      getTopInventory

      +
      +
      public static org.bukkit.inventory.Inventory getTopInventory(org.bukkit.event.inventory.InventoryEvent event)
      +
      Retrieves the top inventory from an InventoryEvent using reflection.
      +
      +
      Parameters:
      +
      event - The inventory event.
      +
      Returns:
      +
      The top inventory (e.g. chest, crafting grid).
      +
      +
      +
      +
    • +
    • +
      +

      getTitle

      +
      +
      public static String getTitle(org.bukkit.event.inventory.InventoryEvent event)
      +
      Retrieves an inventory's title reflectively. +

      + Some Bukkit versions moved getTitle() into subclasses, so this + method uses superclass traversal for compatibility.

      +
      +
      Parameters:
      +
      event - The inventory event.
      +
      Returns:
      +
      Title of the inventory window.
      +
      +
      +
      +
    • +
    • +
      +

      getMethod

      +
      +
      public static Method getMethod(Class<?> clazz, + String name, + Class<?>... parameterTypes)
      +
      +
      +
    • +
    • +
      +

      invokeMethod

      +
      +
      public static void invokeMethod(Method method, + Object instance, + Object... args)
      +
      +
      +
    • +
    • +
      +

      setFieldValue

      +
      +
      public static void setFieldValue(Object instance, + String fieldName, + Object value)
      +
      +
      +
    • +
    • +
      +

      extractUrlFromBase64

      +
      +
      public static String extractUrlFromBase64(String base64Texture)
      +
      Extracts the skin texture URL from a Base64-encoded JSON texture payload.
      +
      +
      Parameters:
      +
      base64Texture - A Base64-encoded Mojang skin texture string.
      +
      Returns:
      +
      The texture URL inside the JSON.
      +
      +
      +
      +
    • +
    • +
      +

      hasDisplayname

      +
      +
      public static boolean hasDisplayname(org.bukkit.inventory.meta.ItemMeta item)
      +
      Checks if an item has a display name. Supports Bukkit name API changes + introduced in Minecraft 1.20.5.
      +
      +
      Parameters:
      +
      item - ItemMeta to inspect.
      +
      Returns:
      +
      True if the item has a custom name.
      +
      +
      +
      +
    • +
    • +
      +

      getDisplayname

      +
      +
      public static String getDisplayname(org.bukkit.inventory.meta.ItemMeta item)
      +
      Retrieves the display name from an item.
      +
      +
      Parameters:
      +
      item - ItemMeta from which to read the name.
      +
      Returns:
      +
      The custom display name.
      +
      +
      +
      +
    • +
    • +
      +

      setDisplayname

      +
      +
      public static org.bukkit.inventory.meta.ItemMeta setDisplayname(org.bukkit.inventory.ItemStack item, + String name)
      +
      Sets an item's display name using version-aware APIs. +

      + In 1.20.5+: +

        +
      • New naming system applies to all items except potion variants.
      • +
      +
      +
      Parameters:
      +
      item - The item whose meta should be modified.
      +
      name - The new display name (supports color codes).
      +
      Returns:
      +
      The updated ItemMeta with the new name applied.
      +
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/CooldownManager.Cooldown.html b/docs/me/mehboss/utils/CooldownManager.Cooldown.html new file mode 100644 index 0000000..25926af --- /dev/null +++ b/docs/me/mehboss/utils/CooldownManager.Cooldown.html @@ -0,0 +1,275 @@ + + + + +CooldownManager.Cooldown (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+

Class CooldownManager.Cooldown

+
+
java.lang.Object +
me.mehboss.utils.CooldownManager.Cooldown
+
+
+
+
+
Enclosing class:
+
CooldownManager
+
+
+
public static class CooldownManager.Cooldown +extends Object
+
Represents a cooldown timer for a specific recipe. +

+ A cooldown consists of: +

    +
  • The recipe ID
  • +
  • The cooldown duration (seconds)
  • +
  • The timestamp when the cooldown started
  • +
+
+
+
+ +
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      Cooldown

      +
      +
      public Cooldown(String recipeID, + long durationSeconds)
      +
      Creates a new cooldown timer starting immediately.
      +
      +
      Parameters:
      +
      recipeID - The recipe to restrict.
      +
      durationSeconds - Duration in seconds.
      +
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      getRecipeID

      +
      +
      public String getRecipeID()
      +
      +
      Returns:
      +
      The recipe ID this cooldown belongs to.
      +
      +
      +
      +
    • +
    • +
      +

      getDuration

      +
      +
      public long getDuration()
      +
      +
      Returns:
      +
      The cooldown duration in seconds.
      +
      +
      +
      +
    • +
    • +
      +

      getStartTime

      +
      +
      public long getStartTime()
      +
      +
      Returns:
      +
      The timestamp when the cooldown began (ms).
      +
      +
      +
      +
    • +
    • +
      +

      getTimeLeft

      +
      +
      public long getTimeLeft()
      +
      Calculates how many seconds remain.
      +
      +
      Returns:
      +
      Seconds left, never negative.
      +
      +
      +
      +
    • +
    • +
      +

      isExpired

      +
      +
      public boolean isExpired()
      +
      Checks whether the cooldown has completed.
      +
      +
      Returns:
      +
      true if expired, else false.
      +
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/CooldownManager.html b/docs/me/mehboss/utils/CooldownManager.html new file mode 100644 index 0000000..74f0ab3 --- /dev/null +++ b/docs/me/mehboss/utils/CooldownManager.html @@ -0,0 +1,371 @@ + + + + +CooldownManager (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+

Class CooldownManager

+
+
java.lang.Object +
me.mehboss.utils.CooldownManager
+
+
+
+
+
public class CooldownManager +extends Object
+
Manages cooldown timers for players on a per-recipe basis. +

+ This class tracks cooldowns using a nested map structure: +

+   Player UUID → (Recipe ID → Cooldown instance)
+ 
+ + It supports: +
    +
  • Adding cooldowns
  • +
  • Checking active cooldowns
  • +
  • Auto-removing expired cooldowns
  • +
  • Saving and restoring cooldown data
  • +
  • Querying remaining cooldown time
  • +
+ + Cooldowns are represented by the CooldownManager.Cooldown inner class.
+
+
+
+ +
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      CooldownManager

      +
      +
      public CooldownManager()
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      addCooldown

      +
      +
      public void addCooldown(UUID player, + CooldownManager.Cooldown cd)
      +
      Adds a cooldown for a specific player.
      +
      +
      Parameters:
      +
      player - The player UUID.
      +
      cd - The cooldown to apply.
      +
      +
      +
      +
    • +
    • +
      +

      getCooldown

      +
      +
      public CooldownManager.Cooldown getCooldown(UUID player, + String recipeID)
      +
      Retrieves a cooldown for a player and recipe.
      +
      +
      Parameters:
      +
      player - The player UUID.
      +
      recipeID - The ID of the recipe.
      +
      Returns:
      +
      The cooldown, or null if none exists.
      +
      +
      +
      +
    • +
    • +
      +

      hasCooldown

      +
      +
      public boolean hasCooldown(UUID player, + String recipeID)
      +
      Checks whether a cooldown is active for the given player and recipe. +

      + This method automatically removes expired cooldowns.

      +
      +
      Parameters:
      +
      player - The player UUID.
      +
      recipeID - The recipe ID.
      +
      Returns:
      +
      true if the cooldown exists and is still active.
      +
      +
      +
      +
    • +
    • +
      +

      removeCooldown

      +
      +
      public void removeCooldown(UUID player, + String recipeID)
      +
      Removes a cooldown for a specific recipe and player.
      +
      +
      Parameters:
      +
      player - The player UUID.
      +
      recipeID - The recipe ID to remove.
      +
      +
      +
      +
    • +
    • +
      +

      getTimeLeft

      +
      +
      public long getTimeLeft(UUID player, + String recipeID)
      +
      Gets the number of seconds left on a cooldown. +

      + Automatically clears expired cooldowns.

      +
      +
      Parameters:
      +
      player - The player UUID.
      +
      recipeID - The recipe ID.
      +
      Returns:
      +
      Seconds remaining, or 0 if expired or not found.
      +
      +
      +
      +
    • +
    • +
      +

      cleanExpired

      +
      +
      public void cleanExpired()
      +
      Cleans all expired cooldowns from memory. +

      + Useful for periodic cleanup tasks.

      +
      +
      +
    • +
    • +
      +

      getCooldowns

      +
      +
      public Map<UUID,Map<String,CooldownManager.Cooldown>> getCooldowns()
      +
      +
      +
    • +
    • +
      +

      setCooldowns

      +
      +
      public void setCooldowns(Map<UUID,Map<String,CooldownManager.Cooldown>> map)
      +
      Restores cooldowns from a saved map. +

      + Existing cooldowns are wiped and replaced.

      +
      +
      Parameters:
      +
      map - Cooldowns previously saved.
      +
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/InvalidRecipeException.html b/docs/me/mehboss/utils/InvalidRecipeException.html new file mode 100644 index 0000000..4c5827a --- /dev/null +++ b/docs/me/mehboss/utils/InvalidRecipeException.html @@ -0,0 +1,184 @@ + + + + +InvalidRecipeException (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+

Class InvalidRecipeException

+
+
java.lang.Object +
java.lang.Throwable +
java.lang.Exception +
java.lang.RuntimeException +
me.mehboss.utils.InvalidRecipeException
+
+
+
+
+
+
+
+
All Implemented Interfaces:
+
Serializable
+
+
+
public class InvalidRecipeException +extends RuntimeException
+
Thrown when a recipe fails validation or contains invalid data. +

+ This exception signals that a recipe cannot be processed due to issues such as: +

    +
  • Missing required fields
  • +
  • Invalid item definitions
  • +
  • Malformed ingredient lists
  • +
  • Unsupported recipe configuration
  • +
+ +

It extends RuntimeException, allowing it to be thrown without + requiring explicit handling or declaration.

+
+
See Also:
+
+ +
+
+
+
+
+ +
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      InvalidRecipeException

      +
      +
      public InvalidRecipeException(String message)
      +
      Constructs a new InvalidRecipeException with a descriptive error message.
      +
      +
      Parameters:
      +
      message - A message explaining why the recipe is invalid.
      +
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/InventoryUtils.html b/docs/me/mehboss/utils/InventoryUtils.html new file mode 100644 index 0000000..defeffb --- /dev/null +++ b/docs/me/mehboss/utils/InventoryUtils.html @@ -0,0 +1,448 @@ + + + + +InventoryUtils (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+

Class InventoryUtils

+
+
java.lang.Object +
me.mehboss.utils.InventoryUtils
+
+
+
+
+
public class InventoryUtils +extends Object
+
Utility class for handling various inventory-related operations such as: +
    +
  • Custom click interaction behavior
  • +
  • Stack merging logic
  • +
  • Space calculations for inventory storage
  • +
  • Detecting empty inventory slots
  • +
+ +

This class is stateless and all methods are static.

+
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    Constructors
    +
    +
    Constructor
    +
    Description
    + +
     
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    +
    +
    Modifier and Type
    +
    Method
    +
    Description
    +
    static void
    +
    calculateClickedSlot(org.bukkit.event.inventory.InventoryClickEvent event)
    +
    +
    Wrapper method that reads the cursor and clicked item directly from the event.
    +
    +
    static void
    +
    calculateClickedSlot(org.bukkit.event.inventory.InventoryClickEvent event, + org.bukkit.inventory.ItemStack cursor, + org.bukkit.inventory.ItemStack currentItem)
    +
    +
    Manually computes the behavior of clicking inside an inventory, overriding + Bukkit's default logic.
    +
    +
    static int
    +
    getInventorySpace(org.bukkit.entity.Player p, + org.bukkit.inventory.ItemStack item)
    +
    +
    Computes available space for a specific item in a player's inventory.
    +
    +
    static int
    +
    getInventorySpace(org.bukkit.inventory.Inventory inventory, + org.bukkit.inventory.ItemStack item)
    +
    +
    Computes available space in an inventory for one specific item type.
    +
    +
    static int
    +
    getInventorySpace(org.bukkit.inventory.ItemStack[] contents, + org.bukkit.inventory.ItemStack item)
    +
    +
    Computes total available capacity for an item based on empty slots and + partially filled stacks that match.
    +
    +
    static boolean
    +
    hasEmptySpaces(org.bukkit.entity.Player p, + int count)
    +
    +
    Checks whether a player has at least a specified number of empty slots.
    +
    +
    static boolean
    +
    hasInventorySpace(org.bukkit.entity.Player p, + org.bukkit.inventory.ItemStack item)
    +
    +
    Checks if a player has room for the given item.
    +
    +
    static boolean
    +
    hasInventorySpace(org.bukkit.inventory.Inventory inventory, + org.bukkit.inventory.ItemStack itemStack)
    +
    +
    Checks if an inventory has space for one stack of the given item.
    +
    +
    static boolean
    +
    hasInventorySpace(org.bukkit.inventory.Inventory inventory, + org.bukkit.inventory.ItemStack itemStack, + int amount)
    +
    +
    Checks if an inventory has room for a given number of a specific item.
    +
    +
    static boolean
    +
    hasInventorySpace(org.bukkit.inventory.ItemStack[] contents, + org.bukkit.inventory.ItemStack itemStack)
    +
    +
    Checks if inventory contents have enough space for the given item.
    +
    +
    static boolean
    +
    isAirOrNull(org.bukkit.inventory.ItemStack item)
    +
    +
    Checks if an item is null or AIR.
    +
    +
    +
    +
    +
    +

    Methods inherited from class java.lang.Object

    +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      InventoryUtils

      +
      +
      public InventoryUtils()
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      calculateClickedSlot

      +
      +
      public static void calculateClickedSlot(org.bukkit.event.inventory.InventoryClickEvent event)
      +
      Wrapper method that reads the cursor and clicked item directly from the event.
      +
      +
      Parameters:
      +
      event - The inventory click event.
      +
      +
      +
      +
    • +
    • +
      +

      calculateClickedSlot

      +
      +
      public static void calculateClickedSlot(org.bukkit.event.inventory.InventoryClickEvent event, + org.bukkit.inventory.ItemStack cursor, + org.bukkit.inventory.ItemStack currentItem)
      +
      Manually computes the behavior of clicking inside an inventory, overriding + Bukkit's default logic. Supports custom stack behavior for both left and + right clicks. + +

      Actions supported:

      +
        +
      • Left-click stack swapping
      • +
      • Left-click stack merge
      • +
      • Right-click single-item placement
      • +
      • Right-click stack merge
      • +
      +
      +
      Parameters:
      +
      event - The click event being processed.
      +
      cursor - The item on the player's cursor.
      +
      currentItem - The item currently in the clicked slot.
      +
      +
      +
      +
    • +
    • +
      +

      isAirOrNull

      +
      +
      public static boolean isAirOrNull(org.bukkit.inventory.ItemStack item)
      +
      Checks if an item is null or AIR.
      +
      +
      Parameters:
      +
      item - The item to check.
      +
      Returns:
      +
      True if null or AIR.
      +
      +
      +
      +
    • +
    • +
      +

      hasInventorySpace

      +
      +
      public static boolean hasInventorySpace(org.bukkit.inventory.Inventory inventory, + org.bukkit.inventory.ItemStack itemStack, + int amount)
      +
      Checks if an inventory has room for a given number of a specific item.
      +
      +
      Parameters:
      +
      inventory - Inventory to check.
      +
      itemStack - The item type being added.
      +
      amount - Number of stacks to fit.
      +
      Returns:
      +
      True if enough space exists.
      +
      +
      +
      +
    • +
    • +
      +

      hasInventorySpace

      +
      +
      public static boolean hasInventorySpace(org.bukkit.inventory.Inventory inventory, + org.bukkit.inventory.ItemStack itemStack)
      +
      Checks if an inventory has space for one stack of the given item.
      +
      +
      Parameters:
      +
      inventory - Inventory to check.
      +
      itemStack - Item being added.
      +
      Returns:
      +
      True if enough space exists.
      +
      +
      +
      +
    • +
    • +
      +

      hasInventorySpace

      +
      +
      public static boolean hasInventorySpace(org.bukkit.inventory.ItemStack[] contents, + org.bukkit.inventory.ItemStack itemStack)
      +
      Checks if inventory contents have enough space for the given item.
      +
      +
      Parameters:
      +
      contents - Inventory contents.
      +
      itemStack - Item being added.
      +
      Returns:
      +
      True if space is available.
      +
      +
      +
      +
    • +
    • +
      +

      hasInventorySpace

      +
      +
      public static boolean hasInventorySpace(org.bukkit.entity.Player p, + org.bukkit.inventory.ItemStack item)
      +
      Checks if a player has room for the given item.
      +
      +
      Parameters:
      +
      p - Player whose inventory will be checked.
      +
      item - Item the player wants to add.
      +
      Returns:
      +
      True if enough space exists.
      +
      +
      +
      +
    • +
    • +
      +

      getInventorySpace

      +
      +
      public static int getInventorySpace(org.bukkit.entity.Player p, + org.bukkit.inventory.ItemStack item)
      +
      Computes available space for a specific item in a player's inventory.
      +
      +
      Parameters:
      +
      p - Player to check.
      +
      item - Item type that may be added.
      +
      Returns:
      +
      The number of items that can fit.
      +
      +
      +
      +
    • +
    • +
      +

      getInventorySpace

      +
      +
      public static int getInventorySpace(org.bukkit.inventory.Inventory inventory, + org.bukkit.inventory.ItemStack item)
      +
      Computes available space in an inventory for one specific item type.
      +
      +
      Parameters:
      +
      inventory - The inventory to scan.
      +
      item - The item type being checked.
      +
      Returns:
      +
      The total number of items that can fit.
      +
      +
      +
      +
    • +
    • +
      +

      getInventorySpace

      +
      +
      public static int getInventorySpace(org.bukkit.inventory.ItemStack[] contents, + org.bukkit.inventory.ItemStack item)
      +
      Computes total available capacity for an item based on empty slots and + partially filled stacks that match.
      +
      +
      Parameters:
      +
      contents - Inventory contents.
      +
      item - Item type being evaluated.
      +
      Returns:
      +
      Total capacity for the item.
      +
      +
      +
      +
    • +
    • +
      +

      hasEmptySpaces

      +
      +
      public static boolean hasEmptySpaces(org.bukkit.entity.Player p, + int count)
      +
      Checks whether a player has at least a specified number of empty slots.
      +
      +
      Parameters:
      +
      p - The player whose inventory to check.
      +
      count - Minimum number of empty slots needed.
      +
      Returns:
      +
      True if at least count empty slots exist.
      +
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/MetaChecks.html b/docs/me/mehboss/utils/MetaChecks.html new file mode 100644 index 0000000..1d689e3 --- /dev/null +++ b/docs/me/mehboss/utils/MetaChecks.html @@ -0,0 +1,207 @@ + + + + +MetaChecks (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+

Class MetaChecks

+
+
java.lang.Object +
me.mehboss.utils.MetaChecks
+
+
+
+
+
public class MetaChecks +extends Object
+
Handles all metadata comparison logic used during special recipe matching. +

+ Provides utility checks for matching item materials, display names, + and custom identifiers. This class is used internally by RecipeUtil + and recipe handlers to determine whether an input item matches a + defined ingredient.

+
+
+
+ +
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      MetaChecks

      +
      +
      public MetaChecks()
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      itemsMatch

      +
      +
      public Boolean itemsMatch(String recipeName, + org.bukkit.inventory.ItemStack item, + RecipeUtil.Ingredient ingredient)
      +
      Compares an input item against a recipe ingredient definition. +

      + Validates: +

        +
      • null vs non-null mismatches
      • +
      • material type
      • +
      • custom identifier match (if present)
      • +
      • display name match (if present)
      • +
      +
      +
      Parameters:
      +
      recipeName - name of the recipe (used for debug output)
      +
      item - the actual input item
      +
      ingredient - expected ingredient definition
      +
      Returns:
      +
      true if the item satisfies all required checks
      +
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/RecipeConditions.Block.TimeRange.html b/docs/me/mehboss/utils/RecipeConditions.Block.TimeRange.html new file mode 100644 index 0000000..7f4e1c7 --- /dev/null +++ b/docs/me/mehboss/utils/RecipeConditions.Block.TimeRange.html @@ -0,0 +1,252 @@ + + + + +RecipeConditions.Block.TimeRange (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+

Class RecipeConditions.Block.TimeRange

+
+
java.lang.Object +
me.mehboss.utils.RecipeConditions.Block.TimeRange
+
+
+
+
+
Enclosing class:
+
RecipeConditions.Block
+
+
+
public static final class RecipeConditions.Block.TimeRange +extends Object
+
Represents a range of valid in-game times for recipe conditions. +

+ A range may be normal (e.g., 0–12000) or wrap around the + midnight boundary (e.g., 23000–2000). The contains(int) + method automatically supports both.

+
+
+
+ +
+
+
    + +
  • +
    +

    Field Details

    +
      +
    • +
      +

      min

      +
      +
      public final int min
      +
      +
      +
    • +
    • +
      +

      max

      +
      +
      public final int max
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      TimeRange

      +
      +
      public TimeRange(int min, + int max)
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      contains

      +
      +
      public boolean contains(int t)
      +
      +
      +
    • +
    • +
      +

      asString

      +
      +
      public String asString()
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/RecipeConditions.Block.html b/docs/me/mehboss/utils/RecipeConditions.Block.html new file mode 100644 index 0000000..701587c --- /dev/null +++ b/docs/me/mehboss/utils/RecipeConditions.Block.html @@ -0,0 +1,365 @@ + + + + +RecipeConditions.Block (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+

Class RecipeConditions.Block

+
+
java.lang.Object +
me.mehboss.utils.RecipeConditions.Block
+
+
+
+
+
Enclosing class:
+
RecipeConditions
+
+
+
public static final class RecipeConditions.Block +extends Object
+
Conditions related to the world or block environment in which + the recipe is executed.
+
+
+
+ +
+
+
    + +
  • +
    +

    Field Details

    + +
    +
  • + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      Block

      +
      +
      public Block()
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      isEmpty

      +
      +
      public boolean isEmpty()
      +
      +
      +
    • +
    • +
      +

      test

      +
      +
      public boolean test(org.bukkit.Location loc, + @Nullable + org.bukkit.entity.Player player)
      +
      Fast-path test (no messages collected).
      +
      +
      +
    • +
    • +
      +

      test

      +
      +
      public boolean test(org.bukkit.Location loc, + @Nullable + org.bukkit.entity.Player player, + @Nullable + List<String> outReasons)
      +
      Tests this block against a location and (optionally) a player. If outReasons + is non-null, collects ALL failure messages and still returns false. If + outReasons is null, short-circuits on first failure for performance.
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/RecipeConditions.ConditionSet.html b/docs/me/mehboss/utils/RecipeConditions.ConditionSet.html new file mode 100644 index 0000000..79c7999 --- /dev/null +++ b/docs/me/mehboss/utils/RecipeConditions.ConditionSet.html @@ -0,0 +1,285 @@ + + + + +RecipeConditions.ConditionSet (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+

Class RecipeConditions.ConditionSet

+
+
java.lang.Object +
me.mehboss.utils.RecipeConditions.ConditionSet
+
+
+
+
+
Enclosing class:
+
RecipeConditions
+
+
+
public static final class RecipeConditions.ConditionSet +extends Object
+
The full condition set for a recipe.
+
+
+
+
    + +
  • +
    +

    Field Summary

    +
    Fields
    +
    +
    Modifier and Type
    +
    Field
    +
    Description
    + + +
    +
    “conditions” or “conditions_all” block (AND).
    +
    + + +
    +
    “conditions_any” is a list of OR blocks.
    +
    +
    +
    +
  • + +
  • +
    +

    Constructor Summary

    +
    Constructors
    +
    +
    Constructor
    +
    Description
    + +
     
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    +
    +
    Modifier and Type
    +
    Method
    +
    Description
    +
    boolean
    + +
     
    +
    boolean
    +
    test(org.bukkit.Location loc, + org.bukkit.entity.Player player)
    +
    +
    Fast-path test (no messages).
    +
    +
    boolean
    +
    test(org.bukkit.Location loc, + org.bukkit.entity.Player player, + List<String> outReasons)
    +
    +
    Overall logic: (all == null || all passes) AND (any is empty OR at least one + any-block passes) + + If outReasons is non-null, messages are added only for failing parts.
    +
    +
    +
    +
    +
    +

    Methods inherited from class java.lang.Object

    +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Field Details

    +
      +
    • +
      +

      all

      +
      + +
      “conditions” or “conditions_all” block (AND).
      +
      +
      +
    • +
    • +
      +

      any

      +
      + +
      “conditions_any” is a list of OR blocks. If non-empty, at least one of these + blocks must pass.
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      ConditionSet

      +
      +
      public ConditionSet()
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      isEmpty

      +
      +
      public boolean isEmpty()
      +
      +
      +
    • +
    • +
      +

      test

      +
      +
      public boolean test(org.bukkit.Location loc, + @Nullable + org.bukkit.entity.Player player)
      +
      Fast-path test (no messages).
      +
      +
      +
    • +
    • +
      +

      test

      +
      +
      public boolean test(org.bukkit.Location loc, + @Nullable + org.bukkit.entity.Player player, + @Nullable + List<String> outReasons)
      +
      Overall logic: (all == null || all passes) AND (any is empty OR at least one + any-block passes) + + If outReasons is non-null, messages are added only for failing parts.
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/RecipeConditions.html b/docs/me/mehboss/utils/RecipeConditions.html new file mode 100644 index 0000000..d9fda53 --- /dev/null +++ b/docs/me/mehboss/utils/RecipeConditions.html @@ -0,0 +1,232 @@ + + + + +RecipeConditions (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+

Class RecipeConditions

+
+
java.lang.Object +
me.mehboss.utils.RecipeConditions
+
+
+
+
+
public final class RecipeConditions +extends Object
+
Represents all optional conditions that may be required for a recipe + to be considered valid before crafting/smelting/brewing/etc. +

+ This container focuses on external world/state conditions such as + time-of-day, biome, weather, world allow/deny lists, and more. +

+ Each category of conditions is grouped into sub-objects. If a category + contains no values, that category is ignored.

+
+
+
+ +
+
+
    + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      getMoonPhase

      +
      +
      public static int getMoonPhase(org.bukkit.World world)
      +
      Calculates the current moon phase for the given world. +

      + Minecraft cycles the moon every 8 in-game days. + This method determines the phase by taking the world's total time + and converting it into a day index, then modulo 8. +

      + Moon phases (0–7): +

        +
      • 0 – Full Moon
      • +
      • 1 – Waning Gibbous
      • +
      • 2 – Last Quarter
      • +
      • 3 – Waning Crescent
      • +
      • 4 – New Moon
      • +
      • 5 – Waxing Crescent
      • +
      • 6 – First Quarter
      • +
      • 7 – Waxing Gibbous
      • +
      +
      +
      Parameters:
      +
      world - the world to read time information from
      +
      Returns:
      +
      an integer 0–7 representing the current moon phase
      +
      +
      +
      +
    • +
    • +
      +

      parseConditionSet

      +
      +
      public static RecipeConditions.ConditionSet parseConditionSet(org.bukkit.configuration.ConfigurationSection root)
      +
      Parse an entire condition set from the per-recipe section.
      +
      +
      +
    • +
    • +
      +

      parseBlock

      +
      +
      public static RecipeConditions.Block parseBlock(org.bukkit.configuration.ConfigurationSection s)
      +
      Parse a single AND block + (world/world_deny/biome/biome_deny/time/weather/advancement/moon_phase).
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/RecipeUtil.Ingredient.html b/docs/me/mehboss/utils/RecipeUtil.Ingredient.html new file mode 100644 index 0000000..fbfbeae --- /dev/null +++ b/docs/me/mehboss/utils/RecipeUtil.Ingredient.html @@ -0,0 +1,677 @@ + + + + +RecipeUtil.Ingredient (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+

Class RecipeUtil.Ingredient

+
+
java.lang.Object +
me.mehboss.utils.RecipeUtil.Ingredient
+
+
+
+
+
Enclosing class:
+
RecipeUtil
+
+
+
public static class RecipeUtil.Ingredient +extends Object
+
Represents a single ingredient used in a recipe. An Ingredient may define + a specific ItemStack, a base Material, a list of acceptable material + choices, custom display/name matching options, and metadata such as slot + position, amount, and model data. + +

Ingredients are created with an abbreviation (the shaped-crafting key + character) and may be configured with additional properties depending on + the recipe type.

+ +

This class acts as a data container. Matching logic is performed elsewhere + when validating recipe input.

+
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    Constructors
    +
    +
    Constructor
    +
    Description
    +
    Ingredient(String abbreviation, + org.bukkit.Material material)
    +
    +
    Parameterized constructor.
    +
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    +
    +
    Modifier and Type
    +
    Method
    +
    Description
    +
    void
    +
    addMaterialChoice(org.bukkit.Material material)
    +
    +
    Adds an additional valid Material for this ingredient.
    +
    + + +
    +
    Getter for the abbreviation of the ingredient, like G for grass Used when + shaping the recipe
    +
    +
    int
    + +
    +
    Getter for the amount requirement
    +
    +
    int
    + +
    +
    Getter for an ingredients model data
    +
    + + +
    +
    Getter for the ingredient displayname
    +
    + + +
    +
    Getter for an ingredients identifier
    +
    +
    org.bukkit.inventory.ItemStack
    + +
    +
    Getter for an ingredient itemstack
    +
    +
    org.bukkit.Material
    + +
    +
    Getter for the material of the ingredient
    +
    +
    List<org.bukkit.Material>
    + +
    +
    Gets all additional materials that may satisfy this ingredient.
    +
    +
    org.bukkit.material.MaterialData
    + +
    +
    Deprecated. +
    Use modern RecipeChoice or + Material APIs instead.
    +
    +
    +
    int
    + +
    +
    Getter for the ingredient slot
    +
    + + +
    +
    Getter for if an ingredient has CMD
    +
    +
    boolean
    + +
    +
    Getter for if the ingredient has a displayname set
    +
    +
    boolean
    + +
    +
    Getter for if the ingredient has an identifier set
    +
    +
    boolean
    + +
    +
    Checks whether this ingredient has multiple valid materials.
    +
    +
    boolean
    + +
    +
    Deprecated. +
    Use modern RecipeChoice or + Material APIs instead.
    +
    +
    +
    boolean
    + +
    +
    Getter for checking if the ingredient is empty
    +
    +
    void
    +
    setAmount(int amount)
    +
    +
    Setter for the amount requirement
    +
    +
    void
    + +
    +
    Setter for an ingredients custom model data
    +
    +
    void
    +
    setDisplayName(String displayName)
    +
    +
    Setter for the ingredients displayname
    +
    +
    void
    +
    setIdentifier(String identifier)
    +
    +
    Setter for the identifier of the ingredient Requires the ingredient to be + tagged with another Custom Recipe
    +
    +
    void
    +
    setItem(org.bukkit.inventory.ItemStack item)
    +
    +
    Setter for the itemstack of the ingredient
    +
    +
    void
    +
    setMaterial(org.bukkit.Material material)
    +
    +
    Setter for the material of the ingredient + + This should NEVER be used, except internally!
    +
    +
    void
    +
    setMaterialData(org.bukkit.material.MaterialData data)
    +
    +
    Deprecated. +
    Use modern RecipeChoice or + Material APIs instead.
    +
    +
    +
    void
    +
    setSlot(int slot)
    +
    +
    Setter for the slot the ingredient is set to, not required for shapeless
    +
    +
    +
    +
    +
    +

    Methods inherited from class java.lang.Object

    +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      Ingredient

      +
      +
      public Ingredient(String abbreviation, + org.bukkit.Material material)
      +
      Parameterized constructor. Initializes an Ingredient object with specified + abbreviation and material
      +
      +
      Parameters:
      +
      abbreviation - the letter of the ingredient
      +
      material - the Material type for the ingredient, can be null or air
      +
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      setItem

      +
      +
      public void setItem(org.bukkit.inventory.ItemStack item)
      +
      Setter for the itemstack of the ingredient
      +
      +
      Parameters:
      +
      item - the itemstack for the ingredient to have, not required
      +
      +
      +
      +
    • +
    • +
      +

      getItem

      +
      +
      public org.bukkit.inventory.ItemStack getItem()
      +
      Getter for an ingredient itemstack
      +
      +
      +
    • +
    • +
      +

      setDisplayName

      +
      +
      public void setDisplayName(String displayName)
      +
      Setter for the ingredients displayname
      +
      +
      Parameters:
      +
      displayname - the name the ingredient is required to have
      +
      +
      +
      +
    • +
    • +
      +

      setIdentifier

      +
      +
      public void setIdentifier(String identifier)
      +
      Setter for the identifier of the ingredient Requires the ingredient to be + tagged with another Custom Recipe
      +
      +
      Parameters:
      +
      identifier - the identifier/tag the ingredient is required to have
      +
      +
      +
      +
    • +
    • +
      +

      getIdentifier

      +
      +
      public String getIdentifier()
      +
      Getter for an ingredients identifier
      +
      +
      +
    • +
    • +
      +

      hasIdentifier

      +
      +
      public boolean hasIdentifier()
      +
      Getter for if the ingredient has an identifier set
      +
      +
      +
    • +
    • +
      +

      setSlot

      +
      +
      public void setSlot(int slot)
      +
      Setter for the slot the ingredient is set to, not required for shapeless
      +
      +
      Parameters:
      +
      slot - the inventory slot the ingredient is set to
      +
      Throws:
      +
      ArrayIndexOutOfBoundsException - if the slot is not within 1-9
      +
      +
      +
      +
    • +
    • +
      +

      getSlot

      +
      +
      public int getSlot()
      +
      Getter for the ingredient slot
      +
      +
      +
    • +
    • +
      +

      setAmount

      +
      +
      public void setAmount(int amount)
      +
      Setter for the amount requirement
      +
      +
      Parameters:
      +
      amount - the amount of the ingredient the recipe requires
      +
      +
      +
      +
    • +
    • +
      +

      getAmount

      +
      +
      public int getAmount()
      +
      Getter for the amount requirement
      +
      +
      +
    • +
    • +
      +

      isEmpty

      +
      +
      public boolean isEmpty()
      +
      Getter for checking if the ingredient is empty
      +
      +
      +
    • +
    • +
      +

      getDisplayName

      +
      +
      public String getDisplayName()
      +
      Getter for the ingredient displayname
      +
      +
      +
    • +
    • +
      +

      hasDisplayName

      +
      +
      public boolean hasDisplayName()
      +
      Getter for if the ingredient has a displayname set
      +
      +
      +
    • +
    • +
      +

      getMaterial

      +
      +
      public org.bukkit.Material getMaterial()
      +
      Getter for the material of the ingredient
      +
      +
      +
    • +
    • +
      +

      setMaterial

      +
      +
      public void setMaterial(org.bukkit.Material material)
      +
      Setter for the material of the ingredient + + This should NEVER be used, except internally!
      +
      +
      Parameters:
      +
      material - the material to be used
      +
      +
      +
      +
    • +
    • +
      +

      getMaterialData

      +
      +
      @Deprecated +public org.bukkit.material.MaterialData getMaterialData()
      +
      Deprecated. +
      Use modern RecipeChoice or + Material APIs instead. MaterialData was + removed after Minecraft 1.13.
      +
      +
      Gets the legacy MaterialData of this ingredient. + +

      + Only valid on Minecraft versions prior to 1.13. On newer versions this will + always return null. +

      +
      +
      Returns:
      +
      the legacy MaterialData, or null if not set
      +
      +
      +
      +
    • +
    • +
      +

      setMaterialData

      +
      +
      @Deprecated +public void setMaterialData(org.bukkit.material.MaterialData data)
      +
      Deprecated. +
      Use modern RecipeChoice or + Material APIs instead. MaterialData was + removed after Minecraft 1.13.
      +
      +
      Sets the legacy MaterialData for this ingredient. + +

      + Only valid on Minecraft versions prior to 1.13. On newer versions this call + has no effect. +

      +
      +
      Parameters:
      +
      data - the legacy MaterialData
      +
      +
      +
      +
    • +
    • +
      +

      addMaterialChoice

      +
      +
      public void addMaterialChoice(org.bukkit.Material material)
      +
      Adds an additional valid Material for this ingredient. + +

      + When material choices are defined, the ingredient will match if the input + item's material is any of those added here. +

      +
      +
      Parameters:
      +
      material - a valid material option for this ingredient
      +
      +
      +
      +
    • +
    • +
      +

      getMaterialChoices

      +
      +
      public List<org.bukkit.Material> getMaterialChoices()
      +
      Gets all additional materials that may satisfy this ingredient.
      +
      +
      Returns:
      +
      a list of valid material choices
      +
      +
      +
      +
    • +
    • +
      +

      hasMaterialData

      +
      +
      @Deprecated +public boolean hasMaterialData()
      +
      Deprecated. +
      Use modern RecipeChoice or + Material APIs instead. MaterialData was + removed after Minecraft 1.13.
      +
      +
      Checks whether this ingredient has legacy MaterialData. + +

      + Always returns false on Minecraft versions 1.13 and newer. +

      +
      +
      Returns:
      +
      true if legacy MaterialData is set and running on a + legacy server, otherwise false
      +
      +
      +
      +
    • +
    • +
      +

      hasMaterialChoices

      +
      +
      public boolean hasMaterialChoices()
      +
      Checks whether this ingredient has multiple valid materials.
      +
      +
      Returns:
      +
      true if one or more material choices are defined
      +
      +
      +
      +
    • +
    • +
      +

      getAbbreviation

      +
      +
      public String getAbbreviation()
      +
      Getter for the abbreviation of the ingredient, like G for grass Used when + shaping the recipe
      +
      +
      +
    • +
    • +
      +

      hasCustomModelData

      +
      +
      public Boolean hasCustomModelData()
      +
      Getter for if an ingredient has CMD
      +
      +
      +
    • +
    • +
      +

      setCustomModelData

      +
      +
      public void setCustomModelData(int data)
      +
      Setter for an ingredients custom model data
      +
      +
      Parameters:
      +
      data - the custom model data of the ingredient
      +
      +
      +
      +
    • +
    • +
      +

      getCustomModelData

      +
      +
      public int getCustomModelData()
      +
      Getter for an ingredients model data
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/RecipeUtil.Recipe.RecipeType.html b/docs/me/mehboss/utils/RecipeUtil.Recipe.RecipeType.html new file mode 100644 index 0000000..be40cf7 --- /dev/null +++ b/docs/me/mehboss/utils/RecipeUtil.Recipe.RecipeType.html @@ -0,0 +1,350 @@ + + + + +RecipeUtil.Recipe.RecipeType (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+

Enum Class RecipeUtil.Recipe.RecipeType

+
+
java.lang.Object +
java.lang.Enum<RecipeUtil.Recipe.RecipeType> +
me.mehboss.utils.RecipeUtil.Recipe.RecipeType
+
+
+
+
+
+
All Implemented Interfaces:
+
Serializable, Comparable<RecipeUtil.Recipe.RecipeType>, java.lang.constant.Constable
+
+
+
Enclosing class:
+
RecipeUtil.Recipe
+
+
+
public static enum RecipeUtil.Recipe.RecipeType +extends Enum<RecipeUtil.Recipe.RecipeType>
+
+
+
+ +
+
+ +
+ +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/RecipeUtil.Recipe.html b/docs/me/mehboss/utils/RecipeUtil.Recipe.html new file mode 100644 index 0000000..48dbfe6 --- /dev/null +++ b/docs/me/mehboss/utils/RecipeUtil.Recipe.html @@ -0,0 +1,1102 @@ + + + + +RecipeUtil.Recipe (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+

Class RecipeUtil.Recipe

+
+
java.lang.Object +
me.mehboss.utils.RecipeUtil.Recipe
+
+
+
+
+
Direct Known Subclasses:
+
BrewingRecipeData, CookingRecipeData, CraftingRecipeData, WorkstationRecipeData
+
+
+
Enclosing class:
+
RecipeUtil
+
+
+
public static class RecipeUtil.Recipe +extends Object
+
Represents a fully defined custom recipe used by the CustomRecipes API. +

+ A Recipe stores its output item, ingredient layout, behavior flags, + permissions, cooldowns, optional commands, and metadata such as whether + the recipe is shaped, shapeless, furnace-based, etc. +

+ +

Only validated recipes should be added through RecipeUtil.createRecipe(me.mehboss.utils.RecipeUtil.Recipe).

+ +

Key features:

+
    +
  • result – the final ItemStack produced by crafting.
  • +
  • ingredients – a list of ingredient entries, optionally exact.
  • +
  • commands – commands executed when the recipe is crafted.
  • +
  • name/key – human-readable name and NamespacedKey identifier.
  • +
  • disabledWorlds – worlds where this recipe cannot be used.
  • +
  • flags – behavior options such as exactChoice, ignoreData, + ignoreModelData, ignoreNames, discoverable, grantItem, etc.
  • +
  • shape rows – row1, row2, row3 for shaped crafting layouts.
  • +
  • cooldown – optional per-player cooldown before recrafting.
  • +
+ +

This class serves as a simple data container. Logic is handled in + RecipeUtil during registration and validation.

+
+
+
+ +
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      Recipe

      +
      +
      public Recipe(String name)
      +
      Parameterized constructor. Initializes a Recipe object with specified name
      +
      +
      Parameters:
      +
      name - the name of the recipe
      +
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      getBookCategory

      +
      +
      public String getBookCategory()
      +
      Getter for setBookCategory
      +
      +
      Returns:
      +
      the Category enum the recipe belongs to in the recipe book.
      +
      +
      +
      +
    • +
    • +
      +

      setBookCategory

      +
      +
      public void setBookCategory(String category)
      +
      Sets the category the recipe will be shown within the vanilla recipe book.
      +
      +
      Parameters:
      +
      category - CraftingBookCategory or CookingBookCategory, defaults to MISC
      +
      +
      +
      +
    • +
    • +
      +

      isDiscoverable

      +
      +
      public boolean isDiscoverable()
      +
      Getter for setDiscoverable
      +
      +
      Returns:
      +
      true if the recipe is discoverable, false otherwise.
      +
      +
      +
      +
    • +
    • +
      +

      setDiscoverable

      +
      +
      public void setDiscoverable(Boolean discoverable)
      +
      Sets whether or not a recipe is going to be discovered automatically
      +
      +
      Parameters:
      +
      discoverable - true or false boolean
      +
      +
      +
      +
    • +
    • +
      +

      isCustomItem

      +
      +
      public boolean isCustomItem()
      +
      Getter for whether or not a recipe result is of a custom item
      +
      +
      Returns:
      +
      true if the recipe output is custom, false otherwise.
      +
      +
      +
      +
    • +
    • +
      +

      setCustomItem

      +
      +
      public void setCustomItem(String customItem)
      +
      Sets an ID for custom item results
      +
      +
      Parameters:
      +
      customItem - the ID of the custom item
      +
      +
      +
      +
    • +
    • +
      +

      getCustomItemID

      +
      +
      public String getCustomItemID()
      +
      Getter for the custom ID output
      +
      +
      Returns:
      +
      the customItem ID
      +
      +
      +
      +
    • +
    • +
      +

      isExactChoice

      +
      +
      public boolean isExactChoice()
      +
      Getter for setExactChoice
      +
      +
      Returns:
      +
      true if the recipe is exactChoice, false otherwise.
      +
      +
      +
      +
    • +
    • +
      +

      setExactChoice

      +
      +
      public void setExactChoice(Boolean exactChoice)
      +
      Sets whether or not a recipe is going to be using exactChoice.
      +
      +
      Parameters:
      +
      exactChoice - true or false boolean
      +
      +
      +
      +
    • +
    • +
      +

      isPlaceable

      +
      +
      public boolean isPlaceable()
      +
      Getter for setPlaceable
      +
      +
      Returns:
      +
      true if the recipe can be placed down, false otherwise.
      +
      +
      +
      +
    • +
    • +
      +

      setPlaceable

      +
      +
      public void setPlaceable(Boolean placeable)
      +
      Sets whether or not a recipe is allowed to be placed.
      +
      +
      Parameters:
      +
      placeable - true or false boolean
      +
      +
      +
      +
    • +
    • +
      +

      isActive

      +
      +
      public boolean isActive()
      +
      Getter for setActive.
      +
      +
      Returns:
      +
      true if the recipe is enabled, false otherwise.
      +
      +
      +
      +
    • +
    • +
      +

      setActive

      +
      +
      public void setActive(Boolean status)
      +
      Enables or disables a recipe
      +
      +
      Parameters:
      +
      status - true or false boolean
      +
      +
      +
      +
    • +
    • +
      +

      getName

      +
      +
      public String getName()
      +
      Getter for the recipe name
      +
      +
      Returns:
      +
      what the recipe name is
      +
      +
      +
      +
    • +
    • +
      +

      setRow

      +
      +
      public void setRow(int row, + String shape)
      +
      Setter for the shaped recipe rows, required
      +
      +
      Parameters:
      +
      row - the recipe row
      +
      shape - the shape for the row (example: XDX)
      +
      Throws:
      +
      ArrayIndexOutOfBoundsException - if the integer specified is not rows + 1-3
      +
      +
      +
      +
    • +
    • +
      +

      getRow

      +
      +
      public String getRow(int row)
      +
      Getter for the shaped recipe rows
      +
      +
      Parameters:
      +
      row - the recipe row
      +
      Throws:
      +
      ArrayIndexOutOfBoundsException - if the integer specified is not rows + 1-3
      +
      +
      +
      +
    • +
    • +
      +

      setType

      +
      +
      public void setType(RecipeUtil.Recipe.RecipeType type)
      +
      Setter for the RecipeType, not required and defaults to SHAPED
      +
      +
      Parameters:
      +
      SHAPELESS - the recipe type will be shapeless
      +
      SHAPED - the recipe type will be shaped
      +
      FURNACE - the recipe type will be a furnace recipe
      +
      STONECUTTER - the recipe type will be a stonecutter recipe
      +
      +
      +
      +
    • +
    • +
      +

      getType

      +
      +
      public RecipeUtil.Recipe.RecipeType getType()
      +
      Getter for the RecipeType
      +
      +
      +
    • +
    • +
      +

      setTagged

      +
      +
      public void setTagged(boolean tagged)
      +
      Setter for whether the result is tagged with the key
      +
      +
      Parameters:
      +
      tagged - tags if true, otherwise does not tag
      +
      Throws:
      +
      InvalidRecipeException - if the recipe result is null
      +
      InvalidRecipeException - if there is no key found
      +
      +
      +
      +
    • +
    • +
      +

      isCustomTagged

      +
      +
      public boolean isCustomTagged()
      +
      Getter for checking if the recipe is tagged with the key
      +
      +
      +
    • +
    • +
      +

      hasKey

      +
      +
      public boolean hasKey()
      +
      Getter for if the recipe has a NamedspaceKey
      +
      +
      +
    • +
    • +
      +

      setKey

      +
      +
      public void setKey(String key)
      +
      Setter for the NamedspaceKey, required
      +
      +
      Parameters:
      +
      key - the NamedspaceKey
      +
      +
      +
      +
    • +
    • +
      +

      getKey

      +
      +
      public String getKey()
      +
      Getter for the NamedspaceKey
      +
      +
      +
    • +
    • +
      +

      hasCooldown

      +
      +
      public boolean hasCooldown()
      +
      Getter for if the recipe has a cooldown set
      +
      +
      +
    • +
    • +
      +

      setCooldown

      +
      +
      public void setCooldown(long cooldown)
      +
      Setter for the recipe cooldown
      +
      +
      Parameters:
      +
      cooldown - the cooldown in seconds
      +
      +
      +
      +
    • +
    • +
      +

      getCooldown

      +
      +
      public long getCooldown()
      +
      Getter for the cooldown
      +
      +
      +
    • +
    • +
      +

      setIgnoreData

      +
      +
      public void setIgnoreData(boolean ignoreData)
      +
      Setter for if the recipe ignores MetaData
      +
      +
      Parameters:
      +
      ignoreData - true to ignore, otherwise false
      +
      +
      +
      +
    • +
    • +
      +

      getIgnoreData

      +
      +
      public boolean getIgnoreData()
      +
      Getter for ignoreData
      +
      +
      +
    • +
    • +
      +

      setIgnoreNames

      +
      +
      public void setIgnoreNames(boolean ignoreNames)
      +
      Setter for if the recipe ignores names
      +
      +
      Parameters:
      +
      ignoreNames - true to ignore, otherwise false
      +
      +
      +
      +
    • +
    • +
      +

      getIgnoreNames

      +
      +
      public boolean getIgnoreNames()
      +
      Getter for ignoreNames
      +
      +
      +
    • +
    • +
      +

      setIgnoreModelData

      +
      +
      public void setIgnoreModelData(boolean ignoreModelData)
      +
      Setter for if the recipe ignores CustomModelData
      +
      +
      Parameters:
      +
      ignoreModelData - true to ignore, otherwise false
      +
      +
      +
      +
    • +
    • +
      +

      getIgnoreModelData

      +
      +
      public boolean getIgnoreModelData()
      +
      Getter for ignoreModelData
      +
      +
      +
    • +
    • +
      +

      setResult

      +
      +
      public void setResult(org.bukkit.inventory.ItemStack result)
      +
      Setter for the recipe result, required
      +
      +
      Parameters:
      +
      result - the ItemStack for the recipe result
      +
      +
      +
      +
    • +
    • +
      +

      getResult

      +
      +
      public org.bukkit.inventory.ItemStack getResult()
      +
      Getter for the recipe result
      +
      +
      +
    • +
    • +
      +

      addIngredient

      +
      +
      public void addIngredient(RecipeUtil.Ingredient ingredient)
      +
      Adds an Ingredient object to the recipe, required Requires 9 ingredients for + crafting, 1 otherwise
      +
      +
      Parameters:
      +
      ingredient - a finalized Ingredient for the recipe
      +
      +
      +
      +
    • +
    • +
      +

      getAllIngredientTypes

      +
      +
      public ArrayList<org.bukkit.Material> getAllIngredientTypes()
      +
      Compiles all ingredient materials of a recipe
      +
      +
      +
    • +
    • +
      +

      getIngredients

      +
      +
      public ArrayList<RecipeUtil.Ingredient> getIngredients()
      +
      Getter for the recipe ingredients
      +
      +
      +
    • +
    • +
      +

      getIngredientSize

      +
      +
      public int getIngredientSize()
      +
      Getter for the amount of ingredients the recipe has
      +
      +
      +
    • +
    • +
      +

      getSlot

      +
      +
      public RecipeUtil.Ingredient getSlot(int slot) + throws ArrayIndexOutOfBoundsException
      +
      Getter for an ingredient slot
      +
      +
      Parameters:
      +
      slot - integer of the slot (1-9)
      +
      Throws:
      +
      ArrayIndexOutOfBoundsException - if param is not 1-9
      +
      +
      +
      +
    • +
    • +
      +

      setPerm

      +
      +
      public void setPerm(String permission)
      +
      Setter for the permission needed to craft the recipe
      +
      +
      Parameters:
      +
      permission - the permission required
      +
      +
      +
      +
    • +
    • +
      +

      getPerm

      +
      +
      public String getPerm()
      +
      Getter for the permission needed to craft the recipe, can be null
      +
      +
      +
    • +
    • +
      +

      hasPerm

      +
      +
      public boolean hasPerm()
      +
      +
      +
    • +
    • +
      +

      addDisabledWorld

      +
      +
      public void addDisabledWorld(String world)
      +
      Adds a world to the noCraft list
      +
      +
      Parameters:
      +
      world - the world to not allow crafting of the recipe in
      +
      +
      +
      +
    • +
    • +
      +

      getDisabledWorlds

      +
      +
      public ArrayList<String> getDisabledWorlds()
      +
      Getter for worlds the recipe can not be crafted in
      +
      +
      +
    • +
    • +
      +

      setGrantItem

      +
      +
      public void setGrantItem(Boolean grantItem)
      +
      Sets whether the recipe output should be granted after a command is performed
      +
      +
      +
    • +
    • +
      +

      isGrantItem

      +
      +
      public boolean isGrantItem()
      +
      Gets whether the recipe output should be granted after a command is performed
      +
      +
      Returns:
      +
      true if the recipe is granted, false otherwise.
      +
      +
      +
      +
    • +
    • +
      +

      hasCommands

      +
      +
      public boolean hasCommands()
      +
      Gets whether the recipe is an item or a command
      +
      +
      Returns:
      +
      true if the recipe is a command, false otherwise
      +
      +
      +
      +
    • +
    • +
      +

      setCommands

      +
      +
      public void setCommands(List<String> commands)
      +
      Sets the commands to perform upon crafting a recipe
      +
      +
      Parameters:
      +
      commands - the list of command strings
      +
      +
      +
      +
    • +
    • +
      +

      getCommand

      +
      +
      public List<String> getCommand()
      +
      Gets the commands to perform upon crafting a recipe
      +
      +
      Returns:
      +
      the list of commands to be performed
      +
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/RecipeUtil.html b/docs/me/mehboss/utils/RecipeUtil.html new file mode 100644 index 0000000..d761934 --- /dev/null +++ b/docs/me/mehboss/utils/RecipeUtil.html @@ -0,0 +1,619 @@ + + + + +RecipeUtil (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+

Class RecipeUtil

+
+
java.lang.Object +
me.mehboss.utils.RecipeUtil
+
+
+
+
+
public class RecipeUtil +extends Object
+
Utility manager and API entry point for creating, validating, and storing + custom recipe definitions inside the CustomRecipes plugin. + +

This class maintains several internal collections used by the plugin:

+ +
    +
  • recipes: A map of recipe names to their corresponding RecipeUtil.Recipe objects.
  • +
  • keyList: A list of all registered NamespacedKey identifiers.
  • +
  • custom_items: A lookup table for custom ItemStacks used by recipes.
  • +
  • SUPPORTED_PLUGINS: A list of optional hookable item systems + (ItemsAdder, MythicMobs, ExecutableItems, Oraxen, Nexo, MMOItems).
  • +
+ +

Recipe Registration

+

The createRecipe(Recipe) method is the primary entry point for + adding recipes to CustomRecipes. Before a recipe is accepted, it is validated + against several rules — if any fail, an InvalidRecipeException is thrown + with a descriptive error message.

+ +

Validation rules include:

+
    +
  • Null or missing key: The recipe must not be null and must have a valid + NamespacedKey.
  • +
  • Ingredient count: + Shaped / shapeless recipes must contain exactly 9 ingredient slots.
  • +
  • Result validity: The output item must exist and cannot be AIR.
  • +
  • Shape integrity: Shaped recipes must have non-null rows (1–3).
  • +
  • Single-input rules: Furnace / stonecutter recipes may only have 1 ingredient.
  • +
+ +

When validation passes, the recipe is stored internally and becomes retrievable + by name or its associated key.

+ +

This class is intended for use by addon developers and internal systems that + need programmatic control over recipe creation and management.

+
+
+
+ +
+
+
    + +
  • +
    +

    Field Details

    +
      +
    • +
      +

      SUPPORTED_PLUGINS

      +
      +
      public List<String> SUPPORTED_PLUGINS
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      RecipeUtil

      +
      +
      public RecipeUtil()
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      createRecipe

      +
      +
      public void createRecipe(RecipeUtil.Recipe recipe)
      +
      Adds a finished Recipe object to the API
      +
      +
      Parameters:
      +
      recipe - the Recipe object
      +
      Throws:
      +
      InvalidRecipeException - if the recipe is null or no NamespacedKey has + been set
      +
      InvalidRecipeException - if the crafting recipe does not have 9 + ingredients
      +
      InvalidRecipeException - if the furnace or stone cutter has more than 1 + ingredient
      +
      InvalidRecipeException - if the result is null or air
      +
      InvalidRecipeException - if the row shape of the shaped recipe is null
      +
      +
      +
      +
    • +
    • +
      +

      removeRecipe

      +
      +
      public void removeRecipe(String recipeName)
      +
      Removes a recipe registered with CR.
      +
      +
      Parameters:
      +
      recipeName - the string name of the recipe you wanting to remove.
      +
      +
      +
      +
    • +
    • +
      +

      registerRecipe

      +
      +
      public void registerRecipe(RecipeUtil.Recipe recipe)
      +
      Loads the specified recipe to the server. Checks and corrects duplicate + NamespacedKey.
      +
      +
      Parameters:
      +
      recipe - the recipe you want to load
      +
      +
      +
      +
    • +
    • +
      +

      reloadRecipes

      +
      +
      public void reloadRecipes()
      +
      Resets and reloads all registered recipes, including CR recipes. Checks and + corrects duplicate NamespacedKey.
      +
      +
      +
    • +
    • +
      +

      getCustomItemPlugin

      +
      +
      public String getCustomItemPlugin(String key)
      +
      Extracts the plugin namespace from a custom item key. +

      + Custom item keys typically follow the format: +

      + pluginNamespace:itemId
      + 
      +
      +
      Parameters:
      +
      key - the full custom item key (e.g. "mmoitems:SWORD_1")
      +
      Returns:
      +
      the namespace before the colon, or null if the key is invalid.
      +
      +
      +
      +
    • +
    • +
      +

      isCustomItem

      +
      +
      public Boolean isCustomItem(String key)
      +
      Checks whether a namespace belongs to a supported custom-item plugin. +

      + This only validates the namespace string (e.g. "mmoitems") — it does not + check if the plugin is installed or if the item exists.

      +
      +
      Parameters:
      +
      key - the plugin namespace to check
      +
      Returns:
      +
      true if the namespace matches a known custom-item plugin.
      +
      +
      +
      +
    • +
    • +
      +

      getResultFromKey

      +
      +
      public org.bukkit.inventory.ItemStack getResultFromKey(String key)
      +
      Getter for a result from a namespaced key.
      +
      +
      Parameters:
      +
      key - the NamespacedKey as a String (e.g., itemsadder:my_item)
      +
      Returns:
      +
      the ItemStack if found, can be null
      +
      +
      +
      +
    • +
    • +
      +

      getKeyFromResult

      +
      +
      public String getKeyFromResult(org.bukkit.inventory.ItemStack item)
      +
      Getter for a key from the result ItemStack
      +
      +
      Parameters:
      +
      item - the ItemStack
      +
      Returns:
      +
      the key that is found, can be null
      +
      +
      +
      +
    • +
    • +
      +

      getRecipeFromKey

      +
      +
      public RecipeUtil.Recipe getRecipeFromKey(String key)
      +
      Getter for a recipe from Namespacedkey
      +
      +
      Parameters:
      +
      key - the NamespacedKey
      +
      Returns:
      +
      the Recipe that is found, can be null
      +
      +
      +
      +
    • +
    • +
      +

      getRecipe

      +
      +
      public RecipeUtil.Recipe getRecipe(String recipeName)
      +
      Getter for a recipe from the Name
      +
      +
      Parameters:
      +
      recipeName - the recipe name
      +
      Returns:
      +
      the Recipe that is found, can be null
      +
      +
      +
      +
    • +
    • +
      +

      getRecipeFromResult

      +
      +
      public RecipeUtil.Recipe getRecipeFromResult(org.bukkit.inventory.ItemStack item)
      +
      Getter for a recipe from the result ItemStack
      +
      +
      Parameters:
      +
      item - the ItemStack
      +
      Returns:
      +
      the Recipe that is found, can be null
      +
      +
      +
      +
    • +
    • +
      +

      getAllRecipes

      +
      +
      public HashMap<String,RecipeUtil.Recipe> getAllRecipes()
      +
      Getter for all recipes registered
      +
      +
      Returns:
      +
      a hashmap of recipes, including CR recipes, can be null
      +
      +
      +
      +
    • +
    • +
      +

      getAllRecipesSortedByResult

      +
      +
      public List<RecipeUtil.Recipe> getAllRecipesSortedByResult(org.bukkit.inventory.ItemStack shown)
      +
      Get all recipes, ordered so that any recipes whose result is similar to the + given result item are first.
      +
      +
      Parameters:
      +
      shown - the result item in the crafting inventory (can be null or AIR)
      +
      Returns:
      +
      ordered list of recipes
      +
      +
      +
      +
    • +
    • +
      +

      getRecipesFromType

      +
      +
      public HashMap<String,RecipeUtil.Recipe> getRecipesFromType(RecipeUtil.Recipe.RecipeType type)
      +
      Getter for all recipes matching a type
      +
      +
      Parameters:
      +
      type - the recipe type
      +
      Returns:
      +
      the hashmap of recipes found, can be null
      +
      +
      +
      +
    • +
    • +
      +

      getAllResults

      +
      +
      public ArrayList<org.bukkit.inventory.ItemStack> getAllResults()
      +
      Getter for all recipe results
      +
      +
      Returns:
      +
      an arraylist of recipe ItemStacks
      +
      +
      +
      +
    • +
    • +
      +

      getRecipeNames

      +
      +
      public ArrayList<String> getRecipeNames()
      +
      Getter for all recipe names
      +
      +
      Returns:
      +
      a set of strings
      +
      +
      +
      +
    • +
    • +
      +

      getRecipeFromFurnaceSource

      +
      +
      public RecipeUtil.Recipe getRecipeFromFurnaceSource(org.bukkit.inventory.ItemStack item)
      +
      Getter for a furnace recipe by the source
      +
      +
      Returns:
      +
      recipe the recipe that matches
      +
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/class-use/CompatibilityUtil.html b/docs/me/mehboss/utils/class-use/CompatibilityUtil.html new file mode 100644 index 0000000..c79ef52 --- /dev/null +++ b/docs/me/mehboss/utils/class-use/CompatibilityUtil.html @@ -0,0 +1,70 @@ + + + + +Uses of Class me.mehboss.utils.CompatibilityUtil (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Class
me.mehboss.utils.CompatibilityUtil

+
+No usage of me.mehboss.utils.CompatibilityUtil +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/class-use/CooldownManager.Cooldown.html b/docs/me/mehboss/utils/class-use/CooldownManager.Cooldown.html new file mode 100644 index 0000000..6a9a37f --- /dev/null +++ b/docs/me/mehboss/utils/class-use/CooldownManager.Cooldown.html @@ -0,0 +1,132 @@ + + + + +Uses of Class me.mehboss.utils.CooldownManager.Cooldown (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Class
me.mehboss.utils.CooldownManager.Cooldown

+
+
Packages that use CooldownManager.Cooldown
+
+
Package
+
Description
+ +
+
Contains general-purpose utility classes used throughout the plugin.
+
+
+
+ +
+
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/class-use/CooldownManager.html b/docs/me/mehboss/utils/class-use/CooldownManager.html new file mode 100644 index 0000000..5fad88a --- /dev/null +++ b/docs/me/mehboss/utils/class-use/CooldownManager.html @@ -0,0 +1,96 @@ + + + + +Uses of Class me.mehboss.utils.CooldownManager (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Class
me.mehboss.utils.CooldownManager

+
+
Packages that use CooldownManager
+
+
Package
+
Description
+ +
+
Core recipe management system.
+
+
+
+ +
+
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/class-use/InvalidRecipeException.html b/docs/me/mehboss/utils/class-use/InvalidRecipeException.html new file mode 100644 index 0000000..6ecee8a --- /dev/null +++ b/docs/me/mehboss/utils/class-use/InvalidRecipeException.html @@ -0,0 +1,70 @@ + + + + +Uses of Class me.mehboss.utils.InvalidRecipeException (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Class
me.mehboss.utils.InvalidRecipeException

+
+No usage of me.mehboss.utils.InvalidRecipeException +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/class-use/InventoryUtils.html b/docs/me/mehboss/utils/class-use/InventoryUtils.html new file mode 100644 index 0000000..04bae5c --- /dev/null +++ b/docs/me/mehboss/utils/class-use/InventoryUtils.html @@ -0,0 +1,70 @@ + + + + +Uses of Class me.mehboss.utils.InventoryUtils (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Class
me.mehboss.utils.InventoryUtils

+
+No usage of me.mehboss.utils.InventoryUtils +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/class-use/MetaChecks.html b/docs/me/mehboss/utils/class-use/MetaChecks.html new file mode 100644 index 0000000..cf9fdb9 --- /dev/null +++ b/docs/me/mehboss/utils/class-use/MetaChecks.html @@ -0,0 +1,96 @@ + + + + +Uses of Class me.mehboss.utils.MetaChecks (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Class
me.mehboss.utils.MetaChecks

+
+
Packages that use MetaChecks
+
+
Package
+
Description
+ +
+
Core recipe management system.
+
+
+
+ +
+
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/class-use/RecipeConditions.Block.TimeRange.html b/docs/me/mehboss/utils/class-use/RecipeConditions.Block.TimeRange.html new file mode 100644 index 0000000..bc94ea1 --- /dev/null +++ b/docs/me/mehboss/utils/class-use/RecipeConditions.Block.TimeRange.html @@ -0,0 +1,98 @@ + + + + +Uses of Class me.mehboss.utils.RecipeConditions.Block.TimeRange (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Class
me.mehboss.utils.RecipeConditions.Block.TimeRange

+
+ +
+
Package
+
Description
+ +
+
Contains general-purpose utility classes used throughout the plugin.
+
+
+
+ +
+
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/class-use/RecipeConditions.Block.html b/docs/me/mehboss/utils/class-use/RecipeConditions.Block.html new file mode 100644 index 0000000..2be2acd --- /dev/null +++ b/docs/me/mehboss/utils/class-use/RecipeConditions.Block.html @@ -0,0 +1,122 @@ + + + + +Uses of Class me.mehboss.utils.RecipeConditions.Block (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Class
me.mehboss.utils.RecipeConditions.Block

+
+
Packages that use RecipeConditions.Block
+
+
Package
+
Description
+ +
+
Contains general-purpose utility classes used throughout the plugin.
+
+
+
+ +
+
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/class-use/RecipeConditions.ConditionSet.html b/docs/me/mehboss/utils/class-use/RecipeConditions.ConditionSet.html new file mode 100644 index 0000000..b38f16a --- /dev/null +++ b/docs/me/mehboss/utils/class-use/RecipeConditions.ConditionSet.html @@ -0,0 +1,130 @@ + + + + +Uses of Class me.mehboss.utils.RecipeConditions.ConditionSet (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Class
me.mehboss.utils.RecipeConditions.ConditionSet

+
+ +
+
Package
+
Description
+ +
+
Contains general-purpose utility classes used throughout the plugin.
+
+ +
+
Contains data models for defining custom recipe behavior.
+
+
+
+ +
+
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/class-use/RecipeConditions.html b/docs/me/mehboss/utils/class-use/RecipeConditions.html new file mode 100644 index 0000000..09f1ca8 --- /dev/null +++ b/docs/me/mehboss/utils/class-use/RecipeConditions.html @@ -0,0 +1,70 @@ + + + + +Uses of Class me.mehboss.utils.RecipeConditions (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Class
me.mehboss.utils.RecipeConditions

+
+No usage of me.mehboss.utils.RecipeConditions +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/class-use/RecipeUtil.Ingredient.html b/docs/me/mehboss/utils/class-use/RecipeUtil.Ingredient.html new file mode 100644 index 0000000..1419203 --- /dev/null +++ b/docs/me/mehboss/utils/class-use/RecipeUtil.Ingredient.html @@ -0,0 +1,154 @@ + + + + +Uses of Class me.mehboss.utils.RecipeUtil.Ingredient (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Class
me.mehboss.utils.RecipeUtil.Ingredient

+
+
Packages that use RecipeUtil.Ingredient
+
+
Package
+
Description
+ +
+
Contains general-purpose utility classes used throughout the plugin.
+
+ +
+
Contains data models for defining custom recipe behavior.
+
+
+
+ +
+
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/class-use/RecipeUtil.Recipe.RecipeType.html b/docs/me/mehboss/utils/class-use/RecipeUtil.Recipe.RecipeType.html new file mode 100644 index 0000000..7ff6679 --- /dev/null +++ b/docs/me/mehboss/utils/class-use/RecipeUtil.Recipe.RecipeType.html @@ -0,0 +1,139 @@ + + + + +Uses of Enum Class me.mehboss.utils.RecipeUtil.Recipe.RecipeType (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Enum Class
me.mehboss.utils.RecipeUtil.Recipe.RecipeType

+
+ +
+
Package
+
Description
+ +
+
Contains general-purpose utility classes used throughout the plugin.
+
+
+
+ +
+
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/class-use/RecipeUtil.Recipe.html b/docs/me/mehboss/utils/class-use/RecipeUtil.Recipe.html new file mode 100644 index 0000000..741eb64 --- /dev/null +++ b/docs/me/mehboss/utils/class-use/RecipeUtil.Recipe.html @@ -0,0 +1,188 @@ + + + + +Uses of Class me.mehboss.utils.RecipeUtil.Recipe (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Class
me.mehboss.utils.RecipeUtil.Recipe

+
+
Packages that use RecipeUtil.Recipe
+
+
Package
+
Description
+ +
+
Contains general-purpose utility classes used throughout the plugin.
+
+ +
+
Contains data models for defining custom recipe behavior.
+
+
+
+ +
+
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/class-use/RecipeUtil.html b/docs/me/mehboss/utils/class-use/RecipeUtil.html new file mode 100644 index 0000000..3959e4e --- /dev/null +++ b/docs/me/mehboss/utils/class-use/RecipeUtil.html @@ -0,0 +1,105 @@ + + + + +Uses of Class me.mehboss.utils.RecipeUtil (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Class
me.mehboss.utils.RecipeUtil

+
+
Packages that use RecipeUtil
+
+
Package
+
Description
+ +
+
Core recipe management system.
+
+
+
+ +
+
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/data/BrewingRecipeData.html b/docs/me/mehboss/utils/data/BrewingRecipeData.html new file mode 100644 index 0000000..e0decd6 --- /dev/null +++ b/docs/me/mehboss/utils/data/BrewingRecipeData.html @@ -0,0 +1,571 @@ + + + + +BrewingRecipeData (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+

Class BrewingRecipeData

+
+
java.lang.Object +
me.mehboss.utils.RecipeUtil.Recipe +
me.mehboss.utils.data.BrewingRecipeData
+
+
+
+
+
+
public class BrewingRecipeData +extends RecipeUtil.Recipe
+
Stores all extra data required for a custom brewing-stand recipe. +

+ Extends RecipeUtil.Recipe by adding fields used specifically by brewing logic: + ingredient item, fuel item, fuel behavior, bottle requirements, brew-action + behavior, and slot indexing. +

+ This is a flexible data container. Recipes may come from config, code, + integrations, or any other source.

+
+
+
+ +
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      BrewingRecipeData

      +
      +
      public BrewingRecipeData(String name)
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      setBrewIngredient

      +
      +
      public void setBrewIngredient(RecipeUtil.Ingredient ingredientItem)
      +
      Builds and stores the ingredient item used for brewing. + Supports custom identifiers or vanilla material/meta.
      +
      +
      Parameters:
      +
      ingredientItem - the ingredient definition to build the item from
      +
      +
      +
      +
    • +
    • +
      +

      getBrewIngredient

      +
      +
      public org.bukkit.inventory.ItemStack getBrewIngredient()
      +
      Gets the brewing ingredient item.
      +
      +
      Returns:
      +
      ingredient item created for this recipe
      +
      +
      +
      +
    • +
    • +
      +

      setBrewFuel

      +
      +
      public void setBrewFuel(RecipeUtil.Ingredient fuelItem)
      +
      Builds and stores the fuel item used for brewing. + Supports custom identifiers or vanilla material/meta.
      +
      +
      Parameters:
      +
      fuelItem - the fuel definition used to construct the item
      +
      +
      +
      +
    • +
    • +
      +

      getBrewFuel

      +
      +
      public org.bukkit.inventory.ItemStack getBrewFuel()
      +
      Gets the brewing fuel item.
      +
      +
      Returns:
      +
      fuel item created for this recipe
      +
      +
      +
      +
    • +
    • +
      +

      setBrewAction

      +
      +
      public void setBrewAction(BrewAction action)
      +
      Sets how this recipe behaves when brewing completes.
      +
      +
      Parameters:
      +
      action - the brewing action to perform on completion
      +
      +
      +
      +
    • +
    • +
      +

      getBrewAction

      +
      +
      public BrewAction getBrewAction()
      +
      Gets the brew action executed when brewing finishes.
      +
      +
      Returns:
      +
      assigned brew action
      +
      +
      +
      +
    • +
    • +
      +

      setBrewPerfect

      +
      +
      public void setBrewPerfect(boolean perfect)
      +
      Enables or disables exact matching for ingredient and fuel items.
      +
      +
      Parameters:
      +
      perfect - true to require exact item matching
      +
      +
      +
      +
    • +
    • +
      +

      isBrewPerfect

      +
      +
      public boolean isBrewPerfect()
      +
      Checks if ingredient and fuel must match exactly.
      +
      +
      Returns:
      +
      true if strict matching is enabled
      +
      +
      +
      +
    • +
    • +
      +

      setBrewFuelSet

      +
      +
      public void setBrewFuelSet(int value)
      +
      Sets how much internal fuel is added when consuming a fuel item.
      +
      +
      Parameters:
      +
      value - amount of internal fuel to add
      +
      +
      +
      +
    • +
    • +
      +

      getBrewFuelSet

      +
      +
      public int getBrewFuelSet()
      +
      Gets how much internal fuel is added per fuel item.
      +
      +
      Returns:
      +
      fuel added per item
      +
      +
      +
      +
    • +
    • +
      +

      setBrewFuelCharge

      +
      +
      public void setBrewFuelCharge(int value)
      +
      Sets how much internal fuel is consumed per brewing cycle.
      +
      +
      Parameters:
      +
      value - amount of fuel consumed
      +
      +
      +
      +
    • +
    • +
      +

      getBrewFuelCharge

      +
      +
      public int getBrewFuelCharge()
      +
      Gets how much fuel is consumed per brew cycle.
      +
      +
      Returns:
      +
      fuel consumption amount
      +
      +
      +
      +
    • +
    • +
      +

      setBrewIngredientSlot

      +
      +
      public void setBrewIngredientSlot(int slot)
      +
      Sets which slot is used for the ingredient.
      +
      +
      Parameters:
      +
      slot - inventory slot index
      +
      +
      +
      +
    • +
    • +
      +

      getBrewIngredientSlot

      +
      +
      public int getBrewIngredientSlot()
      +
      Gets the slot index used for the ingredient.
      +
      +
      Returns:
      +
      ingredient slot index
      +
      +
      +
      +
    • +
    • +
      +

      setBrewFuelSlot

      +
      +
      public void setBrewFuelSlot(int slot)
      +
      Sets which slot is used for the fuel.
      +
      +
      Parameters:
      +
      slot - inventory slot index
      +
      +
      +
      +
    • +
    • +
      +

      getBrewFuelSlot

      +
      +
      public int getBrewFuelSlot()
      +
      Gets the slot index used for the fuel.
      +
      +
      Returns:
      +
      fuel slot index
      +
      +
      +
      +
    • +
    • +
      +

      setRequiresBottles

      +
      +
      public void setRequiresBottles(boolean requiresBottles)
      +
      Enables or disables bottle validation for brewing results.
      +
      +
      Parameters:
      +
      requiresBottles - true to require bottle-type slots
      +
      +
      +
      +
    • +
    • +
      +

      requiresBottles

      +
      +
      public boolean requiresBottles()
      +
      Checks whether bottle validation is required.
      +
      +
      Returns:
      +
      true if valid bottle items must be present
      +
      +
      +
      +
    • +
    • +
      +

      setRequiredBottleType

      +
      +
      public void setRequiredBottleType(org.bukkit.Material type)
      +
      Sets the type of bottle required for valid brewing output.
      +
      +
      Parameters:
      +
      type - the material representing the required bottle
      +
      +
      +
      +
    • +
    • +
      +

      getRequiredBottleType

      +
      +
      public org.bukkit.Material getRequiredBottleType()
      +
      Gets the required bottle type for this recipe.
      +
      +
      Returns:
      +
      bottle material required
      +
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/data/CookingRecipeData.html b/docs/me/mehboss/utils/data/CookingRecipeData.html new file mode 100644 index 0000000..15e7557 --- /dev/null +++ b/docs/me/mehboss/utils/data/CookingRecipeData.html @@ -0,0 +1,301 @@ + + + + +CookingRecipeData (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+

Class CookingRecipeData

+
+
java.lang.Object +
me.mehboss.utils.RecipeUtil.Recipe +
me.mehboss.utils.data.CookingRecipeData
+
+
+
+
+
+
public class CookingRecipeData +extends RecipeUtil.Recipe
+
Stores extra data for custom furnace-based recipes. +

+ Extends RecipeUtil.Recipe by adding furnace-specific fields such as + source item, cook time, and experience yield. This class is a simple + data container and does not enforce how recipes are created.

+
+
+
+ +
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      CookingRecipeData

      +
      +
      public CookingRecipeData(String name)
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      setSource

      +
      +
      public void setSource(org.bukkit.inventory.ItemStack source)
      +
      Sets the input item that this recipe smelts or cooks.
      +
      +
      Parameters:
      +
      source - the furnace input item
      +
      +
      +
      +
    • +
    • +
      +

      getSource

      +
      +
      public org.bukkit.inventory.ItemStack getSource()
      +
      Gets the input item used by this cooking recipe.
      +
      +
      Returns:
      +
      the source item
      +
      +
      +
      +
    • +
    • +
      +

      setCookTime

      +
      +
      public void setCookTime(int cookTime)
      +
      Sets the cook time in ticks. + (20 ticks = 1 second)
      +
      +
      Parameters:
      +
      cookTime - the number of ticks required to cook
      +
      +
      +
      +
    • +
    • +
      +

      getCookTime

      +
      +
      public int getCookTime()
      +
      Gets how long the recipe takes to cook.
      +
      +
      Returns:
      +
      cook time in ticks
      +
      +
      +
      +
    • +
    • +
      +

      setExperience

      +
      +
      public void setExperience(float experience)
      +
      Sets the experience granted when the recipe is completed.
      +
      +
      Parameters:
      +
      experience - the XP amount awarded
      +
      +
      +
      +
    • +
    • +
      +

      getExperience

      +
      +
      public float getExperience()
      +
      Gets the experience value awarded when the item is cooked.
      +
      +
      Returns:
      +
      experience amount
      +
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/data/CraftingRecipeData.html b/docs/me/mehboss/utils/data/CraftingRecipeData.html new file mode 100644 index 0000000..6e00908 --- /dev/null +++ b/docs/me/mehboss/utils/data/CraftingRecipeData.html @@ -0,0 +1,308 @@ + + + + +CraftingRecipeData (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+

Class CraftingRecipeData

+
+
java.lang.Object +
me.mehboss.utils.RecipeUtil.Recipe +
me.mehboss.utils.data.CraftingRecipeData
+
+
+
+
+
+
public class CraftingRecipeData +extends RecipeUtil.Recipe
+
Stores additional data for custom crafting recipes. +

+ Extends RecipeUtil.Recipe with fields used only by crafting systems, + including leftover item rules, conditional requirements, and + optional recipe grouping. This class acts only as a data container + and does not enforce how a recipe is created.

+
+
+
+ +
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      CraftingRecipeData

      +
      +
      public CraftingRecipeData(String name)
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      addLeftoverItem

      +
      +
      public void addLeftoverItem(String id)
      +
      Setter for adding an ingredient as a leftover
      +
      +
      Parameters:
      +
      id - the id of the ingredient to be leftover.
      +
      +
      +
      +
    • +
    • +
      +

      isLeftover

      +
      +
      public Boolean isLeftover(String id)
      +
      Checks if a MATERIAL is marked as leftover.
      +
      +
      +
    • +
    • +
      +

      getGroup

      +
      +
      public String getGroup()
      +
      Getter for the recipe group. + +

      + Available since Spigot 1.13+ +

      +
      +
      Returns:
      +
      the group string this recipe belongs to. Empty string means no group.
      +
      +
      +
      +
    • +
    • +
      +

      setGroup

      +
      +
      public void setGroup(String group)
      +
      Sets the group of the recipe. Recipes with the same group may be grouped + together when displayed in the client. + +

      + Available since Spigot 1.13+ +

      +
      +
      Parameters:
      +
      group - the group name. Empty string denotes no group.
      +
      +
      +
      +
    • +
    • +
      +

      getConditionSet

      +
      +
      public RecipeConditions.ConditionSet getConditionSet()
      +
      Gets the ConditionSet attached to this recipe. Conditions define extra + requirements for crafting (e.g. world, time, weather).
      +
      +
      Returns:
      +
      the ConditionSet for this recipe, never null
      +
      +
      +
      +
    • +
    • +
      +

      setConditionSet

      +
      +
      public void setConditionSet(RecipeConditions.ConditionSet cs)
      +
      Sets the ConditionSet for this recipe. If cs is null, an empty + ConditionSet is applied instead.
      +
      +
      Parameters:
      +
      cs - the new ConditionSet for this recipe, can be null
      +
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/data/WorkstationRecipeData.html b/docs/me/mehboss/utils/data/WorkstationRecipeData.html new file mode 100644 index 0000000..5ac0791 --- /dev/null +++ b/docs/me/mehboss/utils/data/WorkstationRecipeData.html @@ -0,0 +1,326 @@ + + + + +WorkstationRecipeData (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+

Class WorkstationRecipeData

+
+
java.lang.Object +
me.mehboss.utils.RecipeUtil.Recipe +
me.mehboss.utils.data.WorkstationRecipeData
+
+
+
+
+
+
public class WorkstationRecipeData +extends RecipeUtil.Recipe
+
Stores additional data for workstation-based recipes such as + anvils or stonecutters. +

+ Extends RecipeUtil.Recipe by adding fields related to workstation + behavior, including repair cost, experience output, and optional + grouping. This class is a flexible data container and does not + define how recipes must be created.

+
+
+
+ +
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      WorkstationRecipeData

      +
      +
      public WorkstationRecipeData(String name)
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      setRepairCost

      +
      +
      public void setRepairCost(int cost)
      +
      Sets the repair cost for this recipe (used by anvil mechanics).
      +
      +
      Parameters:
      +
      cost - the repair cost to assign
      +
      +
      +
      +
    • +
    • +
      +

      getRepairCost

      +
      +
      public int getRepairCost()
      +
      Gets the repair cost assigned to this workstation recipe.
      +
      +
      Returns:
      +
      numerical repair cost
      +
      +
      +
      +
    • +
    • +
      +

      hasRepairCost

      +
      +
      public boolean hasRepairCost()
      +
      Checks whether this recipe has a defined repair cost.
      +
      +
      Returns:
      +
      true if repairCost > 0
      +
      +
      +
      +
    • +
    • +
      +

      setExperience

      +
      +
      public void setExperience(float experience)
      +
      Sets the amount of experience granted by this recipe.
      +
      +
      Parameters:
      +
      experience - the XP reward value
      +
      +
      +
      +
    • +
    • +
      +

      getExperience

      +
      +
      public float getExperience()
      +
      Gets the experience reward assigned to this recipe.
      +
      +
      Returns:
      +
      experience value
      +
      +
      +
      +
    • +
    • +
      +

      getGroup

      +
      +
      public String getGroup()
      +
      Gets the group tag assigned to this recipe. +

      + Available since Spigot 1.13+.

      +
      +
      Returns:
      +
      the recipe group name, or empty string if ungrouped
      +
      +
      +
      +
    • +
    • +
      +

      setGroup

      +
      +
      public void setGroup(String group)
      +
      Sets the group this recipe belongs to. + Recipes with the same group may be visually grouped in the client. +

      + Available since Spigot 1.13+.

      +
      +
      Parameters:
      +
      group - the group identifier, or empty string for no group
      +
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/data/class-use/BrewingRecipeData.html b/docs/me/mehboss/utils/data/class-use/BrewingRecipeData.html new file mode 100644 index 0000000..d9bc7dc --- /dev/null +++ b/docs/me/mehboss/utils/data/class-use/BrewingRecipeData.html @@ -0,0 +1,110 @@ + + + + +Uses of Class me.mehboss.utils.data.BrewingRecipeData (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Class
me.mehboss.utils.data.BrewingRecipeData

+
+
Packages that use BrewingRecipeData
+
+
Package
+
Description
+ +
+
Handles custom brewing stand recipes and brewing mechanics.
+
+
+
+ +
+
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/data/class-use/CookingRecipeData.html b/docs/me/mehboss/utils/data/class-use/CookingRecipeData.html new file mode 100644 index 0000000..56dc37d --- /dev/null +++ b/docs/me/mehboss/utils/data/class-use/CookingRecipeData.html @@ -0,0 +1,70 @@ + + + + +Uses of Class me.mehboss.utils.data.CookingRecipeData (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Class
me.mehboss.utils.data.CookingRecipeData

+
+No usage of me.mehboss.utils.data.CookingRecipeData +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/data/class-use/CraftingRecipeData.html b/docs/me/mehboss/utils/data/class-use/CraftingRecipeData.html new file mode 100644 index 0000000..20fa799 --- /dev/null +++ b/docs/me/mehboss/utils/data/class-use/CraftingRecipeData.html @@ -0,0 +1,70 @@ + + + + +Uses of Class me.mehboss.utils.data.CraftingRecipeData (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Class
me.mehboss.utils.data.CraftingRecipeData

+
+No usage of me.mehboss.utils.data.CraftingRecipeData +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/data/class-use/WorkstationRecipeData.html b/docs/me/mehboss/utils/data/class-use/WorkstationRecipeData.html new file mode 100644 index 0000000..5f08277 --- /dev/null +++ b/docs/me/mehboss/utils/data/class-use/WorkstationRecipeData.html @@ -0,0 +1,70 @@ + + + + +Uses of Class me.mehboss.utils.data.WorkstationRecipeData (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Class
me.mehboss.utils.data.WorkstationRecipeData

+
+No usage of me.mehboss.utils.data.WorkstationRecipeData +
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/data/package-summary.html b/docs/me/mehboss/utils/data/package-summary.html new file mode 100644 index 0000000..be4cc63 --- /dev/null +++ b/docs/me/mehboss/utils/data/package-summary.html @@ -0,0 +1,131 @@ + + + + +me.mehboss.utils.data (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+
+

Package me.mehboss.utils.data

+
+
+
+
package me.mehboss.utils.data
+
+
Contains data models for defining custom recipe behavior. +

+ Includes specialized Recipe subclasses such as crafting, cooking, + brewing, and workstation recipes. These classes store configuration + and behavior settings used by the recipe engine.

+
+
+
+
    +
  • + +
  • +
  • +
    +
    Classes
    +
    +
    Class
    +
    Description
    + +
    +
    Stores all extra data required for a custom brewing-stand recipe.
    +
    + +
    +
    Stores extra data for custom furnace-based recipes.
    +
    + +
    +
    Stores additional data for custom crafting recipes.
    +
    + +
    +
    Stores additional data for workstation-based recipes such as + anvils or stonecutters.
    +
    +
    +
    +
  • +
+
+
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/data/package-tree.html b/docs/me/mehboss/utils/data/package-tree.html new file mode 100644 index 0000000..2df0097 --- /dev/null +++ b/docs/me/mehboss/utils/data/package-tree.html @@ -0,0 +1,89 @@ + + + + +me.mehboss.utils.data Class Hierarchy (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Hierarchy For Package me.mehboss.utils.data

+
+Package Hierarchies: + +
+

Class Hierarchy

+ +
+
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/data/package-use.html b/docs/me/mehboss/utils/data/package-use.html new file mode 100644 index 0000000..25f956d --- /dev/null +++ b/docs/me/mehboss/utils/data/package-use.html @@ -0,0 +1,94 @@ + + + + +Uses of Package me.mehboss.utils.data (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Package
me.mehboss.utils.data

+
+
Packages that use me.mehboss.utils.data
+
+
Package
+
Description
+ +
+
Handles custom brewing stand recipes and brewing mechanics.
+
+
+
+ +
+
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/package-summary.html b/docs/me/mehboss/utils/package-summary.html new file mode 100644 index 0000000..be6a731 --- /dev/null +++ b/docs/me/mehboss/utils/package-summary.html @@ -0,0 +1,180 @@ + + + + +me.mehboss.utils (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+ +
+
+

Package me.mehboss.utils

+
+
+
+
package me.mehboss.utils
+
+
Contains general-purpose utility classes used throughout the plugin. +

+ Includes item comparison logic, compatibility helpers, cooldown + management, inventory utilities, meta checks, and shared helper + functions used by multiple recipe systems.

+
+
+
+
    +
  • + +
  • +
  • +
    +
    +
    +
    +
    Class
    +
    Description
    + +
    +
    Utility class providing cross-version compatibility helpers that rely on + reflection to handle differences between Minecraft / Bukkit versions.
    +
    + +
    +
    Manages cooldown timers for players on a per-recipe basis.
    +
    + +
    +
    Represents a cooldown timer for a specific recipe.
    +
    + +
    +
    Thrown when a recipe fails validation or contains invalid data.
    +
    + +
    +
    Utility class for handling various inventory-related operations such as: + + Custom click interaction behavior + Stack merging logic + Space calculations for inventory storage + Detecting empty inventory slots +
    +
    + +
    +
    Handles all metadata comparison logic used during special recipe matching.
    +
    + +
    +
    Represents all optional conditions that may be required for a recipe + to be considered valid before crafting/smelting/brewing/etc.
    +
    + +
    +
    Conditions related to the world or block environment in which + the recipe is executed.
    +
    + +
    +
    Represents a range of valid in-game times for recipe conditions.
    +
    + +
    +
    The full condition set for a recipe.
    +
    + +
    +
    Utility manager and API entry point for creating, validating, and storing + custom recipe definitions inside the CustomRecipes plugin.
    +
    + +
    +
    Represents a single ingredient used in a recipe.
    +
    + +
    +
    Represents a fully defined custom recipe used by the CustomRecipes API.
    +
    + +
     
    +
    +
    +
    +
  • +
+
+
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/package-tree.html b/docs/me/mehboss/utils/package-tree.html new file mode 100644 index 0000000..530ebdb --- /dev/null +++ b/docs/me/mehboss/utils/package-tree.html @@ -0,0 +1,120 @@ + + + + +me.mehboss.utils Class Hierarchy (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Hierarchy For Package me.mehboss.utils

+
+Package Hierarchies: + +
+

Class Hierarchy

+ +
+
+

Enum Class Hierarchy

+ +
+
+
+ +
+
+
+ + diff --git a/docs/me/mehboss/utils/package-use.html b/docs/me/mehboss/utils/package-use.html new file mode 100644 index 0000000..58b911e --- /dev/null +++ b/docs/me/mehboss/utils/package-use.html @@ -0,0 +1,168 @@ + + + + +Uses of Package me.mehboss.utils (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Uses of Package
me.mehboss.utils

+
+
Packages that use me.mehboss.utils
+
+
Package
+
Description
+ +
+
Core recipe management system.
+
+ +
+
Contains general-purpose utility classes used throughout the plugin.
+
+ +
+
Contains data models for defining custom recipe behavior.
+
+
+
+ +
+
+
+ +
+
+
+ + diff --git a/docs/member-search-index.js b/docs/member-search-index.js new file mode 100644 index 0000000..51c4f5f --- /dev/null +++ b/docs/member-search-index.js @@ -0,0 +1 @@ +memberSearchIndex = [{"p":"me.mehboss.utils","c":"CooldownManager","l":"addCooldown(UUID, CooldownManager.Cooldown)","u":"addCooldown(java.util.UUID,me.mehboss.utils.CooldownManager.Cooldown)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"addDisabledWorld(String)","u":"addDisabledWorld(java.lang.String)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"addIngredient(RecipeUtil.Ingredient)","u":"addIngredient(me.mehboss.utils.RecipeUtil.Ingredient)"},{"p":"me.mehboss.utils.data","c":"CraftingRecipeData","l":"addLeftoverItem(String)","u":"addLeftoverItem(java.lang.String)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Ingredient","l":"addMaterialChoice(Material)","u":"addMaterialChoice(org.bukkit.Material)"},{"p":"me.mehboss.utils","c":"RecipeConditions.Block","l":"advancements"},{"p":"me.mehboss.utils","c":"RecipeConditions.ConditionSet","l":"all"},{"p":"me.mehboss.utils","c":"RecipeConditions.Block","l":"allowBiomes"},{"p":"me.mehboss.utils","c":"RecipeConditions.Block","l":"allowWorlds"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe.RecipeType","l":"ANVIL"},{"p":"me.mehboss.utils","c":"RecipeConditions.ConditionSet","l":"any"},{"p":"me.mehboss.commands","c":"CRCommand","l":"args"},{"p":"me.mehboss.utils","c":"RecipeConditions.Block.TimeRange","l":"asString()"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe.RecipeType","l":"BLASTFURNACE"},{"p":"me.mehboss.utils","c":"RecipeConditions.Block","l":"Block()","u":"%3Cinit%3E()"},{"p":"me.mehboss.brewing","c":"BrewAction","l":"brew(BrewerInventory, ItemStack, ItemStack)","u":"brew(org.bukkit.inventory.BrewerInventory,org.bukkit.inventory.ItemStack,org.bukkit.inventory.ItemStack)"},{"p":"me.mehboss.brewing","c":"BrewAction","l":"BrewAction()","u":"%3Cinit%3E()"},{"p":"me.mehboss.brewing","c":"Brewery","l":"Brewery(JavaPlugin)","u":"%3Cinit%3E(org.bukkit.plugin.java.JavaPlugin)"},{"p":"me.mehboss.brewing","c":"BrewEvent","l":"BrewEvent()","u":"%3Cinit%3E()"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe.RecipeType","l":"BREWING_STAND"},{"p":"me.mehboss.brewing","c":"BrewingRecipe","l":"BrewingRecipe()","u":"%3Cinit%3E()"},{"p":"me.mehboss.utils.data","c":"BrewingRecipeData","l":"BrewingRecipeData(String)","u":"%3Cinit%3E(java.lang.String)"},{"p":"me.mehboss.utils","c":"InventoryUtils","l":"calculateClickedSlot(InventoryClickEvent)","u":"calculateClickedSlot(org.bukkit.event.inventory.InventoryClickEvent)"},{"p":"me.mehboss.utils","c":"InventoryUtils","l":"calculateClickedSlot(InventoryClickEvent, ItemStack, ItemStack)","u":"calculateClickedSlot(org.bukkit.event.inventory.InventoryClickEvent,org.bukkit.inventory.ItemStack,org.bukkit.inventory.ItemStack)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe.RecipeType","l":"CAMPFIRE"},{"p":"me.mehboss.utils","c":"CooldownManager","l":"cleanExpired()"},{"p":"me.mehboss.commands","c":"CRCommand","l":"command"},{"p":"me.mehboss.commands","c":"CommandBook","l":"CommandBook()","u":"%3Cinit%3E()"},{"p":"me.mehboss.commands","c":"CommandCrafterDebug","l":"CommandCrafterDebug()","u":"%3Cinit%3E()"},{"p":"me.mehboss.commands","c":"CommandDebug","l":"CommandDebug()","u":"%3Cinit%3E()"},{"p":"me.mehboss.commands","c":"CommandEditItem","l":"CommandEditItem()","u":"%3Cinit%3E()"},{"p":"me.mehboss.commands","c":"CommandGive","l":"CommandGive()","u":"%3Cinit%3E()"},{"p":"me.mehboss.commands","c":"CommandGUI","l":"CommandGUI()","u":"%3Cinit%3E()"},{"p":"me.mehboss.commands","c":"CommandHelp","l":"CommandHelp()","u":"%3Cinit%3E()"},{"p":"me.mehboss.commands","c":"CommandItems","l":"CommandItems()","u":"%3Cinit%3E()"},{"p":"me.mehboss.commands","c":"CommandList","l":"CommandList()","u":"%3Cinit%3E()"},{"p":"me.mehboss.recipe","c":"CommandListener","l":"CommandListener()","u":"%3Cinit%3E()"},{"p":"me.mehboss.commands","c":"CommandRecipe","l":"CommandRecipe()","u":"%3Cinit%3E()"},{"p":"me.mehboss.commands","c":"CommandReload","l":"CommandReload()","u":"%3Cinit%3E()"},{"p":"me.mehboss.commands","c":"CommandRemove","l":"CommandRemove()","u":"%3Cinit%3E()"},{"p":"me.mehboss.commands","c":"CommandShow","l":"CommandShow()","u":"%3Cinit%3E()"},{"p":"me.mehboss.utils","c":"CompatibilityUtil","l":"CompatibilityUtil()","u":"%3Cinit%3E()"},{"p":"me.mehboss.utils","c":"RecipeConditions.ConditionSet","l":"ConditionSet()","u":"%3Cinit%3E()"},{"p":"me.mehboss.utils","c":"RecipeConditions.Block.TimeRange","l":"contains(int)"},{"p":"me.mehboss.utils.data","c":"CookingRecipeData","l":"CookingRecipeData(String)","u":"%3Cinit%3E(java.lang.String)"},{"p":"me.mehboss.utils","c":"CooldownManager.Cooldown","l":"Cooldown(String, long)","u":"%3Cinit%3E(java.lang.String,long)"},{"p":"me.mehboss.recipe","c":"Main","l":"cooldownManager"},{"p":"me.mehboss.utils","c":"CooldownManager","l":"CooldownManager()","u":"%3Cinit%3E()"},{"p":"me.mehboss.recipe","c":"Main","l":"crafterdebug"},{"p":"me.mehboss.utils.data","c":"CraftingRecipeData","l":"CraftingRecipeData(String)","u":"%3Cinit%3E(java.lang.String)"},{"p":"me.mehboss.commands","c":"CRCommand","l":"CRCommand(CommandSender, Command, String, String[])","u":"%3Cinit%3E(org.bukkit.command.CommandSender,org.bukkit.command.Command,java.lang.String,java.lang.String[])"},{"p":"me.mehboss.utils","c":"RecipeUtil","l":"createRecipe(RecipeUtil.Recipe)","u":"createRecipe(me.mehboss.utils.RecipeUtil.Recipe)"},{"p":"me.mehboss.recipe","c":"Main","l":"customConfig"},{"p":"me.mehboss.recipe","c":"Main","l":"debounceMap"},{"p":"me.mehboss.recipe","c":"Main","l":"debug"},{"p":"me.mehboss.utils","c":"RecipeConditions.Block","l":"denyBiomes"},{"p":"me.mehboss.utils","c":"RecipeConditions.Block","l":"denyWorlds"},{"p":"me.mehboss.recipe","c":"Main","l":"disabledrecipe"},{"p":"me.mehboss.utils","c":"CompatibilityUtil","l":"extractUrlFromBase64(String)","u":"extractUrlFromBase64(java.lang.String)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe.RecipeType","l":"fromString(String)","u":"fromString(java.lang.String)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe.RecipeType","l":"FURNACE"},{"p":"me.mehboss.utils","c":"RecipeUtil.Ingredient","l":"getAbbreviation()"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"getAllIngredientTypes()"},{"p":"me.mehboss.utils","c":"RecipeUtil","l":"getAllRecipes()"},{"p":"me.mehboss.utils","c":"RecipeUtil","l":"getAllRecipesSortedByResult(ItemStack)","u":"getAllRecipesSortedByResult(org.bukkit.inventory.ItemStack)"},{"p":"me.mehboss.utils","c":"RecipeUtil","l":"getAllResults()"},{"p":"me.mehboss.utils","c":"RecipeUtil.Ingredient","l":"getAmount()"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"getBookCategory()"},{"p":"me.mehboss.utils.data","c":"BrewingRecipeData","l":"getBrewAction()"},{"p":"me.mehboss.utils.data","c":"BrewingRecipeData","l":"getBrewFuel()"},{"p":"me.mehboss.utils.data","c":"BrewingRecipeData","l":"getBrewFuelCharge()"},{"p":"me.mehboss.utils.data","c":"BrewingRecipeData","l":"getBrewFuelSet()"},{"p":"me.mehboss.utils.data","c":"BrewingRecipeData","l":"getBrewFuelSlot()"},{"p":"me.mehboss.utils.data","c":"BrewingRecipeData","l":"getBrewIngredient()"},{"p":"me.mehboss.utils.data","c":"BrewingRecipeData","l":"getBrewIngredientSlot()"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"getCommand()"},{"p":"me.mehboss.utils.data","c":"CraftingRecipeData","l":"getConditionSet()"},{"p":"me.mehboss.utils.data","c":"CookingRecipeData","l":"getCookTime()"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"getCooldown()"},{"p":"me.mehboss.utils","c":"CooldownManager","l":"getCooldown(UUID, String)","u":"getCooldown(java.util.UUID,java.lang.String)"},{"p":"me.mehboss.utils","c":"CooldownManager","l":"getCooldowns()"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"getCustomItemID()"},{"p":"me.mehboss.utils","c":"RecipeUtil","l":"getCustomItemPlugin(String)","u":"getCustomItemPlugin(java.lang.String)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Ingredient","l":"getCustomModelData()"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"getDisabledWorlds()"},{"p":"me.mehboss.utils","c":"RecipeUtil.Ingredient","l":"getDisplayName()"},{"p":"me.mehboss.utils","c":"CompatibilityUtil","l":"getDisplayname(ItemMeta)","u":"getDisplayname(org.bukkit.inventory.meta.ItemMeta)"},{"p":"me.mehboss.utils","c":"CooldownManager.Cooldown","l":"getDuration()"},{"p":"me.mehboss.utils.data","c":"CookingRecipeData","l":"getExperience()"},{"p":"me.mehboss.utils.data","c":"WorkstationRecipeData","l":"getExperience()"},{"p":"me.mehboss.utils.data","c":"CraftingRecipeData","l":"getGroup()"},{"p":"me.mehboss.utils.data","c":"WorkstationRecipeData","l":"getGroup()"},{"p":"me.mehboss.utils","c":"RecipeUtil.Ingredient","l":"getIdentifier()"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"getIgnoreData()"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"getIgnoreModelData()"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"getIgnoreNames()"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"getIngredients()"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"getIngredientSize()"},{"p":"me.mehboss.brewing","c":"Brewery","l":"getInstance()"},{"p":"me.mehboss.recipe","c":"Main","l":"getInstance()"},{"p":"me.mehboss.utils","c":"InventoryUtils","l":"getInventorySpace(Inventory, ItemStack)","u":"getInventorySpace(org.bukkit.inventory.Inventory,org.bukkit.inventory.ItemStack)"},{"p":"me.mehboss.utils","c":"InventoryUtils","l":"getInventorySpace(ItemStack[], ItemStack)","u":"getInventorySpace(org.bukkit.inventory.ItemStack[],org.bukkit.inventory.ItemStack)"},{"p":"me.mehboss.utils","c":"InventoryUtils","l":"getInventorySpace(Player, ItemStack)","u":"getInventorySpace(org.bukkit.entity.Player,org.bukkit.inventory.ItemStack)"},{"p":"me.mehboss.utils","c":"CompatibilityUtil","l":"getInventoryView(InventoryEvent)","u":"getInventoryView(org.bukkit.event.inventory.InventoryEvent)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Ingredient","l":"getItem()"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"getKey()"},{"p":"me.mehboss.utils","c":"RecipeUtil","l":"getKeyFromResult(ItemStack)","u":"getKeyFromResult(org.bukkit.inventory.ItemStack)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Ingredient","l":"getMaterial()"},{"p":"me.mehboss.utils","c":"RecipeUtil.Ingredient","l":"getMaterialChoices()"},{"p":"me.mehboss.utils","c":"RecipeUtil.Ingredient","l":"getMaterialData()"},{"p":"me.mehboss.utils","c":"CompatibilityUtil","l":"getMethod(Class, String, Class...)","u":"getMethod(java.lang.Class,java.lang.String,java.lang.Class...)"},{"p":"me.mehboss.utils","c":"RecipeConditions","l":"getMoonPhase(World)","u":"getMoonPhase(org.bukkit.World)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"getName()"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"getPerm()"},{"p":"me.mehboss.utils","c":"CompatibilityUtil","l":"getPlayerFromView(Object)","u":"getPlayerFromView(java.lang.Object)"},{"p":"me.mehboss.brewing","c":"Brewery","l":"getPlugin()"},{"p":"me.mehboss.brewing","c":"BrewingRecipe","l":"getRecipe(BrewerInventory)","u":"getRecipe(org.bukkit.inventory.BrewerInventory)"},{"p":"me.mehboss.utils","c":"RecipeUtil","l":"getRecipe(String)","u":"getRecipe(java.lang.String)"},{"p":"me.mehboss.utils","c":"RecipeUtil","l":"getRecipeFromFurnaceSource(ItemStack)","u":"getRecipeFromFurnaceSource(org.bukkit.inventory.ItemStack)"},{"p":"me.mehboss.utils","c":"RecipeUtil","l":"getRecipeFromKey(String)","u":"getRecipeFromKey(java.lang.String)"},{"p":"me.mehboss.utils","c":"RecipeUtil","l":"getRecipeFromResult(ItemStack)","u":"getRecipeFromResult(org.bukkit.inventory.ItemStack)"},{"p":"me.mehboss.utils","c":"CooldownManager.Cooldown","l":"getRecipeID()"},{"p":"me.mehboss.utils","c":"RecipeUtil","l":"getRecipeNames()"},{"p":"me.mehboss.utils","c":"RecipeUtil","l":"getRecipesFromType(RecipeUtil.Recipe.RecipeType)","u":"getRecipesFromType(me.mehboss.utils.RecipeUtil.Recipe.RecipeType)"},{"p":"me.mehboss.recipe","c":"Main","l":"getRecipeUtil()"},{"p":"me.mehboss.utils.data","c":"WorkstationRecipeData","l":"getRepairCost()"},{"p":"me.mehboss.utils.data","c":"BrewingRecipeData","l":"getRequiredBottleType()"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"getResult()"},{"p":"me.mehboss.utils","c":"RecipeUtil","l":"getResultFromKey(String)","u":"getResultFromKey(java.lang.String)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"getRow(int)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Ingredient","l":"getSlot()"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"getSlot(int)"},{"p":"me.mehboss.utils.data","c":"CookingRecipeData","l":"getSource()"},{"p":"me.mehboss.utils","c":"CooldownManager.Cooldown","l":"getStartTime()"},{"p":"me.mehboss.utils","c":"CooldownManager.Cooldown","l":"getTimeLeft()"},{"p":"me.mehboss.utils","c":"CooldownManager","l":"getTimeLeft(UUID, String)","u":"getTimeLeft(java.util.UUID,java.lang.String)"},{"p":"me.mehboss.utils","c":"CompatibilityUtil","l":"getTitle(InventoryEvent)","u":"getTitle(org.bukkit.event.inventory.InventoryEvent)"},{"p":"me.mehboss.utils","c":"CompatibilityUtil","l":"getTopInventory(InventoryEvent)","u":"getTopInventory(org.bukkit.event.inventory.InventoryEvent)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"getType()"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe.RecipeType","l":"GRINDSTONE"},{"p":"me.mehboss.recipe","c":"Main","l":"hasAE"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"hasCommands()"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"hasCooldown()"},{"p":"me.mehboss.utils","c":"CooldownManager","l":"hasCooldown(UUID, String)","u":"hasCooldown(java.util.UUID,java.lang.String)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Ingredient","l":"hasCustomModelData()"},{"p":"me.mehboss.utils","c":"RecipeUtil.Ingredient","l":"hasDisplayName()"},{"p":"me.mehboss.utils","c":"CompatibilityUtil","l":"hasDisplayname(ItemMeta)","u":"hasDisplayname(org.bukkit.inventory.meta.ItemMeta)"},{"p":"me.mehboss.recipe","c":"Main","l":"hasEE"},{"p":"me.mehboss.utils","c":"InventoryUtils","l":"hasEmptySpaces(Player, int)","u":"hasEmptySpaces(org.bukkit.entity.Player,int)"},{"p":"me.mehboss.recipe","c":"Main","l":"hasHavenBags"},{"p":"me.mehboss.utils","c":"RecipeUtil.Ingredient","l":"hasIdentifier()"},{"p":"me.mehboss.utils","c":"InventoryUtils","l":"hasInventorySpace(Inventory, ItemStack)","u":"hasInventorySpace(org.bukkit.inventory.Inventory,org.bukkit.inventory.ItemStack)"},{"p":"me.mehboss.utils","c":"InventoryUtils","l":"hasInventorySpace(Inventory, ItemStack, int)","u":"hasInventorySpace(org.bukkit.inventory.Inventory,org.bukkit.inventory.ItemStack,int)"},{"p":"me.mehboss.utils","c":"InventoryUtils","l":"hasInventorySpace(ItemStack[], ItemStack)","u":"hasInventorySpace(org.bukkit.inventory.ItemStack[],org.bukkit.inventory.ItemStack)"},{"p":"me.mehboss.utils","c":"InventoryUtils","l":"hasInventorySpace(Player, ItemStack)","u":"hasInventorySpace(org.bukkit.entity.Player,org.bukkit.inventory.ItemStack)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"hasKey()"},{"p":"me.mehboss.utils","c":"RecipeUtil.Ingredient","l":"hasMaterialChoices()"},{"p":"me.mehboss.utils","c":"RecipeUtil.Ingredient","l":"hasMaterialData()"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"hasPerm()"},{"p":"me.mehboss.utils.data","c":"WorkstationRecipeData","l":"hasRepairCost()"},{"p":"me.mehboss.utils","c":"RecipeUtil.Ingredient","l":"Ingredient(String, Material)","u":"%3Cinit%3E(java.lang.String,org.bukkit.Material)"},{"p":"me.mehboss.recipe","c":"Main","l":"inInventory"},{"p":"me.mehboss.utils","c":"InvalidRecipeException","l":"InvalidRecipeException(String)","u":"%3Cinit%3E(java.lang.String)"},{"p":"me.mehboss.utils","c":"InventoryUtils","l":"InventoryUtils()","u":"%3Cinit%3E()"},{"p":"me.mehboss.utils","c":"CompatibilityUtil","l":"invokeMethod(Method, Object, Object...)","u":"invokeMethod(java.lang.reflect.Method,java.lang.Object,java.lang.Object...)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"isActive()"},{"p":"me.mehboss.utils","c":"InventoryUtils","l":"isAirOrNull(ItemStack)","u":"isAirOrNull(org.bukkit.inventory.ItemStack)"},{"p":"me.mehboss.utils.data","c":"BrewingRecipeData","l":"isBrewPerfect()"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"isCustomItem()"},{"p":"me.mehboss.utils","c":"RecipeUtil","l":"isCustomItem(String)","u":"isCustomItem(java.lang.String)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"isCustomTagged()"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"isDiscoverable()"},{"p":"me.mehboss.utils","c":"RecipeConditions.Block","l":"isEmpty()"},{"p":"me.mehboss.utils","c":"RecipeConditions.ConditionSet","l":"isEmpty()"},{"p":"me.mehboss.utils","c":"RecipeUtil.Ingredient","l":"isEmpty()"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"isExactChoice()"},{"p":"me.mehboss.utils","c":"CooldownManager.Cooldown","l":"isExpired()"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"isGrantItem()"},{"p":"me.mehboss.commands","c":"CommandGive","l":"isInt(String)","u":"isInt(java.lang.String)"},{"p":"me.mehboss.utils.data","c":"CraftingRecipeData","l":"isLeftover(String)","u":"isLeftover(java.lang.String)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"isPlaceable()"},{"p":"me.mehboss.utils","c":"MetaChecks","l":"itemsMatch(String, ItemStack, RecipeUtil.Ingredient)","u":"itemsMatch(java.lang.String,org.bukkit.inventory.ItemStack,me.mehboss.utils.RecipeUtil.Ingredient)"},{"p":"me.mehboss.commands","c":"CRCommand","l":"label"},{"p":"me.mehboss.recipe","c":"Main","l":"Main()","u":"%3Cinit%3E()"},{"p":"me.mehboss.utils","c":"RecipeConditions.Block.TimeRange","l":"max"},{"p":"me.mehboss.recipe","c":"Main","l":"metaChecks"},{"p":"me.mehboss.utils","c":"MetaChecks","l":"MetaChecks()","u":"%3Cinit%3E()"},{"p":"me.mehboss.utils","c":"RecipeConditions.Block.TimeRange","l":"min"},{"p":"me.mehboss.utils","c":"RecipeConditions.Block","l":"moonPhases"},{"p":"me.mehboss.brewing","c":"Brewery","l":"onBrewClick(InventoryClickEvent)","u":"onBrewClick(org.bukkit.event.inventory.InventoryClickEvent)"},{"p":"me.mehboss.recipe","c":"CommandListener","l":"onCommand(CommandSender, Command, String, String[])","u":"onCommand(org.bukkit.command.CommandSender,org.bukkit.command.Command,java.lang.String,java.lang.String[])"},{"p":"me.mehboss.commands","c":"TabCompletion","l":"onTabComplete(CommandSender, Command, String, String[])","u":"onTabComplete(org.bukkit.command.CommandSender,org.bukkit.command.Command,java.lang.String,java.lang.String[])"},{"p":"me.mehboss.utils","c":"RecipeConditions","l":"parseBlock(ConfigurationSection)","u":"parseBlock(org.bukkit.configuration.ConfigurationSection)"},{"p":"me.mehboss.utils","c":"RecipeConditions","l":"parseConditionSet(ConfigurationSection)","u":"parseConditionSet(org.bukkit.configuration.ConfigurationSection)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"Recipe(String)","u":"%3Cinit%3E(java.lang.String)"},{"p":"me.mehboss.recipe","c":"Main","l":"recipeBook"},{"p":"me.mehboss.recipe","c":"Main","l":"recipeUtil"},{"p":"me.mehboss.utils","c":"RecipeUtil","l":"RecipeUtil()","u":"%3Cinit%3E()"},{"p":"me.mehboss.utils","c":"RecipeUtil","l":"registerRecipe(RecipeUtil.Recipe)","u":"registerRecipe(me.mehboss.utils.RecipeUtil.Recipe)"},{"p":"me.mehboss.utils","c":"RecipeUtil","l":"reloadRecipes()"},{"p":"me.mehboss.utils","c":"CooldownManager","l":"removeCooldown(UUID, String)","u":"removeCooldown(java.util.UUID,java.lang.String)"},{"p":"me.mehboss.commands","c":"CommandRemove","l":"removeRecipe(String)","u":"removeRecipe(java.lang.String)"},{"p":"me.mehboss.utils","c":"RecipeUtil","l":"removeRecipe(String)","u":"removeRecipe(java.lang.String)"},{"p":"me.mehboss.utils.data","c":"BrewingRecipeData","l":"requiresBottles()"},{"p":"me.mehboss.commands","c":"CommandBook","l":"Run(CRCommand)","u":"Run(me.mehboss.commands.CRCommand)"},{"p":"me.mehboss.commands","c":"CommandCrafterDebug","l":"Run(CRCommand)","u":"Run(me.mehboss.commands.CRCommand)"},{"p":"me.mehboss.commands","c":"CommandDebug","l":"Run(CRCommand)","u":"Run(me.mehboss.commands.CRCommand)"},{"p":"me.mehboss.commands","c":"CommandEditItem","l":"Run(CRCommand)","u":"Run(me.mehboss.commands.CRCommand)"},{"p":"me.mehboss.commands","c":"CommandGive","l":"Run(CRCommand)","u":"Run(me.mehboss.commands.CRCommand)"},{"p":"me.mehboss.commands","c":"CommandGUI","l":"Run(CRCommand)","u":"Run(me.mehboss.commands.CRCommand)"},{"p":"me.mehboss.commands","c":"CommandHelp","l":"Run(CRCommand)","u":"Run(me.mehboss.commands.CRCommand)"},{"p":"me.mehboss.commands","c":"CommandItems","l":"Run(CRCommand)","u":"Run(me.mehboss.commands.CRCommand)"},{"p":"me.mehboss.commands","c":"CommandList","l":"Run(CRCommand)","u":"Run(me.mehboss.commands.CRCommand)"},{"p":"me.mehboss.commands","c":"CommandRecipe","l":"Run(CRCommand)","u":"Run(me.mehboss.commands.CRCommand)"},{"p":"me.mehboss.commands","c":"CommandReload","l":"Run(CRCommand)","u":"Run(me.mehboss.commands.CRCommand)"},{"p":"me.mehboss.commands","c":"CommandRemove","l":"Run(CRCommand)","u":"Run(me.mehboss.commands.CRCommand)"},{"p":"me.mehboss.commands","c":"CommandShow","l":"Run(CRCommand)","u":"Run(me.mehboss.commands.CRCommand)"},{"p":"me.mehboss.recipe","c":"Main","l":"saveInventory"},{"p":"me.mehboss.commands","c":"CRCommand","l":"sender"},{"p":"me.mehboss.recipe","c":"Main","l":"serverVersionAtLeast(int, int)","u":"serverVersionAtLeast(int,int)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"setActive(Boolean)","u":"setActive(java.lang.Boolean)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Ingredient","l":"setAmount(int)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"setBookCategory(String)","u":"setBookCategory(java.lang.String)"},{"p":"me.mehboss.utils.data","c":"BrewingRecipeData","l":"setBrewAction(BrewAction)","u":"setBrewAction(me.mehboss.brewing.BrewAction)"},{"p":"me.mehboss.utils.data","c":"BrewingRecipeData","l":"setBrewFuel(RecipeUtil.Ingredient)","u":"setBrewFuel(me.mehboss.utils.RecipeUtil.Ingredient)"},{"p":"me.mehboss.utils.data","c":"BrewingRecipeData","l":"setBrewFuelCharge(int)"},{"p":"me.mehboss.utils.data","c":"BrewingRecipeData","l":"setBrewFuelSet(int)"},{"p":"me.mehboss.utils.data","c":"BrewingRecipeData","l":"setBrewFuelSlot(int)"},{"p":"me.mehboss.utils.data","c":"BrewingRecipeData","l":"setBrewIngredient(RecipeUtil.Ingredient)","u":"setBrewIngredient(me.mehboss.utils.RecipeUtil.Ingredient)"},{"p":"me.mehboss.utils.data","c":"BrewingRecipeData","l":"setBrewIngredientSlot(int)"},{"p":"me.mehboss.utils.data","c":"BrewingRecipeData","l":"setBrewPerfect(boolean)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"setCommands(List)","u":"setCommands(java.util.List)"},{"p":"me.mehboss.utils.data","c":"CraftingRecipeData","l":"setConditionSet(RecipeConditions.ConditionSet)","u":"setConditionSet(me.mehboss.utils.RecipeConditions.ConditionSet)"},{"p":"me.mehboss.utils.data","c":"CookingRecipeData","l":"setCookTime(int)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"setCooldown(long)"},{"p":"me.mehboss.utils","c":"CooldownManager","l":"setCooldowns(Map>)","u":"setCooldowns(java.util.Map)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"setCustomItem(String)","u":"setCustomItem(java.lang.String)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Ingredient","l":"setCustomModelData(int)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"setDiscoverable(Boolean)","u":"setDiscoverable(java.lang.Boolean)"},{"p":"me.mehboss.utils","c":"CompatibilityUtil","l":"setDisplayname(ItemStack, String)","u":"setDisplayname(org.bukkit.inventory.ItemStack,java.lang.String)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Ingredient","l":"setDisplayName(String)","u":"setDisplayName(java.lang.String)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"setExactChoice(Boolean)","u":"setExactChoice(java.lang.Boolean)"},{"p":"me.mehboss.utils.data","c":"CookingRecipeData","l":"setExperience(float)"},{"p":"me.mehboss.utils.data","c":"WorkstationRecipeData","l":"setExperience(float)"},{"p":"me.mehboss.utils","c":"CompatibilityUtil","l":"setFieldValue(Object, String, Object)","u":"setFieldValue(java.lang.Object,java.lang.String,java.lang.Object)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"setGrantItem(Boolean)","u":"setGrantItem(java.lang.Boolean)"},{"p":"me.mehboss.utils.data","c":"CraftingRecipeData","l":"setGroup(String)","u":"setGroup(java.lang.String)"},{"p":"me.mehboss.utils.data","c":"WorkstationRecipeData","l":"setGroup(String)","u":"setGroup(java.lang.String)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Ingredient","l":"setIdentifier(String)","u":"setIdentifier(java.lang.String)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"setIgnoreData(boolean)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"setIgnoreModelData(boolean)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"setIgnoreNames(boolean)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Ingredient","l":"setItem(ItemStack)","u":"setItem(org.bukkit.inventory.ItemStack)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"setKey(String)","u":"setKey(java.lang.String)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Ingredient","l":"setMaterial(Material)","u":"setMaterial(org.bukkit.Material)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Ingredient","l":"setMaterialData(MaterialData)","u":"setMaterialData(org.bukkit.material.MaterialData)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"setPerm(String)","u":"setPerm(java.lang.String)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"setPlaceable(Boolean)","u":"setPlaceable(java.lang.Boolean)"},{"p":"me.mehboss.utils.data","c":"WorkstationRecipeData","l":"setRepairCost(int)"},{"p":"me.mehboss.utils.data","c":"BrewingRecipeData","l":"setRequiredBottleType(Material)","u":"setRequiredBottleType(org.bukkit.Material)"},{"p":"me.mehboss.utils.data","c":"BrewingRecipeData","l":"setRequiresBottles(boolean)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"setResult(ItemStack)","u":"setResult(org.bukkit.inventory.ItemStack)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"setRow(int, String)","u":"setRow(int,java.lang.String)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Ingredient","l":"setSlot(int)"},{"p":"me.mehboss.utils.data","c":"CookingRecipeData","l":"setSource(ItemStack)","u":"setSource(org.bukkit.inventory.ItemStack)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"setTagged(boolean)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe","l":"setType(RecipeUtil.Recipe.RecipeType)","u":"setType(me.mehboss.utils.RecipeUtil.Recipe.RecipeType)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe.RecipeType","l":"SHAPED"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe.RecipeType","l":"SHAPELESS"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe.RecipeType","l":"SMOKER"},{"p":"me.mehboss.brewing","c":"BrewingRecipe","l":"startBrewing(BrewerInventory, BrewingRecipeData)","u":"startBrewing(org.bukkit.inventory.BrewerInventory,me.mehboss.utils.data.BrewingRecipeData)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe.RecipeType","l":"STONECUTTER"},{"p":"me.mehboss.utils","c":"RecipeUtil","l":"SUPPORTED_PLUGINS"},{"p":"me.mehboss.commands","c":"TabCompletion","l":"TabCompletion()","u":"%3Cinit%3E()"},{"p":"me.mehboss.utils","c":"RecipeConditions.Block","l":"test(Location, Player)","u":"test(org.bukkit.Location,org.bukkit.entity.Player)"},{"p":"me.mehboss.utils","c":"RecipeConditions.ConditionSet","l":"test(Location, Player)","u":"test(org.bukkit.Location,org.bukkit.entity.Player)"},{"p":"me.mehboss.utils","c":"RecipeConditions.Block","l":"test(Location, Player, List)","u":"test(org.bukkit.Location,org.bukkit.entity.Player,java.util.List)"},{"p":"me.mehboss.utils","c":"RecipeConditions.ConditionSet","l":"test(Location, Player, List)","u":"test(org.bukkit.Location,org.bukkit.entity.Player,java.util.List)"},{"p":"me.mehboss.utils","c":"RecipeConditions.Block.TimeRange","l":"TimeRange(int, int)","u":"%3Cinit%3E(int,int)"},{"p":"me.mehboss.utils","c":"RecipeConditions.Block","l":"timeRanges"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe.RecipeType","l":"valueOf(String)","u":"valueOf(java.lang.String)"},{"p":"me.mehboss.utils","c":"RecipeUtil.Recipe.RecipeType","l":"values()"},{"p":"me.mehboss.utils","c":"RecipeConditions.Block","l":"weathers"},{"p":"me.mehboss.utils.data","c":"WorkstationRecipeData","l":"WorkstationRecipeData(String)","u":"%3Cinit%3E(java.lang.String)"}];updateSearchResults(); \ No newline at end of file diff --git a/docs/module-search-index.js b/docs/module-search-index.js new file mode 100644 index 0000000..0d59754 --- /dev/null +++ b/docs/module-search-index.js @@ -0,0 +1 @@ +moduleSearchIndex = [];updateSearchResults(); \ No newline at end of file diff --git a/docs/options b/docs/options new file mode 100644 index 0000000..5f2a875 --- /dev/null +++ b/docs/options @@ -0,0 +1,25 @@ +-classpath +'C:/Users/timot/.m2/repository/org/spigotmc/spigot-api/1.21.1-R0.1-SNAPSHOT/spigot-api-1.21.1-R0.1-SNAPSHOT.jar;C:/Users/timot/.m2/repository/com/google/guava/guava/32.1.2-jre/guava-32.1.2-jre.jar;C:/Users/timot/.m2/repository/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar;C:/Users/timot/.m2/repository/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar;C:/Users/timot/.m2/repository/org/checkerframework/checker-qual/3.33.0/checker-qual-3.33.0.jar;C:/Users/timot/.m2/repository/com/google/errorprone/error_prone_annotations/2.18.0/error_prone_annotations-2.18.0.jar;C:/Users/timot/.m2/repository/com/google/j2objc/j2objc-annotations/2.8/j2objc-annotations-2.8.jar;C:/Users/timot/.m2/repository/com/google/code/gson/gson/2.10.1/gson-2.10.1.jar;C:/Users/timot/.m2/repository/org/joml/joml/1.10.5/joml-1.10.5.jar;C:/Users/timot/.m2/repository/net/md-5/bungeecord-chat/1.20-R0.2/bungeecord-chat-1.20-R0.2.jar;C:/Users/timot/.m2/repository/org/yaml/snakeyaml/2.2/snakeyaml-2.2.jar;C:/Users/timot/.m2/repository/me/clip/placeholderapi/2.11.6/placeholderapi-2.11.6.jar;C:/Users/timot/.m2/repository/org/jetbrains/annotations/23.0.0/annotations-23.0.0.jar;C:/Users/timot/.m2/repository/org/bstats/bstats-bukkit/3.0.1/bstats-bukkit-3.0.1.jar;C:/Users/timot/.m2/repository/org/bstats/bstats-base/3.0.1/bstats-base-3.0.1.jar;C:/Users/timot/.m2/repository/net/kyori/adventure-platform-bukkit/4.3.2/adventure-platform-bukkit-4.3.2.jar;C:/Users/timot/.m2/repository/net/kyori/adventure-platform-api/4.3.2/adventure-platform-api-4.3.2.jar;C:/Users/timot/.m2/repository/net/kyori/adventure-api/4.13.1/adventure-api-4.13.1.jar;C:/Users/timot/.m2/repository/net/kyori/adventure-key/4.13.1/adventure-key-4.13.1.jar;C:/Users/timot/.m2/repository/net/kyori/adventure-text-serializer-bungeecord/4.3.2/adventure-text-serializer-bungeecord-4.3.2.jar;C:/Users/timot/.m2/repository/net/kyori/adventure-text-serializer-legacy/4.13.1/adventure-text-serializer-legacy-4.13.1.jar;C:/Users/timot/.m2/repository/net/kyori/adventure-nbt/4.13.1/adventure-nbt-4.13.1.jar;C:/Users/timot/.m2/repository/net/kyori/examination-api/1.3.0/examination-api-1.3.0.jar;C:/Users/timot/.m2/repository/net/kyori/examination-string/1.3.0/examination-string-1.3.0.jar;C:/Users/timot/.m2/repository/net/kyori/adventure-text-serializer-gson/4.13.1/adventure-text-serializer-gson-4.13.1.jar;C:/Users/timot/.m2/repository/net/kyori/adventure-text-serializer-gson-legacy-impl/4.13.1/adventure-text-serializer-gson-legacy-impl-4.13.1.jar;C:/Users/timot/.m2/repository/net/kyori/adventure-platform-facet/4.3.2/adventure-platform-facet-4.3.2.jar;C:/Users/timot/.m2/repository/net/kyori/adventure-platform-viaversion/4.3.2/adventure-platform-viaversion-4.3.2.jar;C:/Users/timot/.m2/repository/com/github/cryptomorin/XSeries/13.3.3/XSeries-13.3.3.jar;C:/Users/timot/.m2/repository/com/willfp/EcoEnchants/12.19.0/EcoEnchants-12.19.0.jar;C:/Users/timot/.m2/repository/io/github/bananapuncher714/nbteditor/7.19.10/nbteditor-7.19.10.jar;C:/Users/timot/.m2/repository/com/github/LoneDev6/api-itemsadder/3.6.1/api-itemsadder-3.6.1.jar;C:/Users/timot/.m2/repository/io/lumine/Mythic-Dist/5.7.2/Mythic-Dist-5.7.2.jar;C:/Users/timot/.m2/repository/io/lumine/MythicLib-dist/1.6.2-SNAPSHOT/MythicLib-dist-1.6.2-SNAPSHOT.jar;C:/Users/timot/.m2/repository/net/Indyuce/MMOItems-API/6.9.5-SNAPSHOT/MMOItems-API-6.9.5-SNAPSHOT.jar;C:/Users/timot/.m2/repository/de/tr7zw/item-nbt-api/2.15.3/item-nbt-api-2.15.3.jar;C:/Users/timot/.m2/repository/io/th0rgal/oraxen/1.189.0/oraxen-1.189.0.jar;C:/Users/timot/.m2/repository/team/unnamed/creative-api/1.7.3/creative-api-1.7.3.jar;C:/Users/timot/.m2/repository/dev/jorel/commandapi-bukkit-shade/9.7.0/commandapi-bukkit-shade-9.7.0.jar;C:/Users/timot/.m2/repository/org/glassfish/javax.json/1.1.4/javax.json-1.1.4.jar;C:/Users/timot/.m2/repository/com/jeff-media/MorePersistentDataTypes/2.4.0/MorePersistentDataTypes-2.4.0.jar;C:/Users/timot/.m2/repository/com/nexomc/nexo/1.3.0/nexo-1.3.0.jar;C:/Users/timot/.m2/repository/com/mojang/authlib/6.0.54/authlib-6.0.54.jar;C:/Users/timot/.m2/repository/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar;C:/Users/timot/.m2/repository/commons-io/commons-io/2.11.0/commons-io-2.11.0.jar;C:/Users/timot/.m2/repository/org/apache/commons/commons-lang3/3.12.0/commons-lang3-3.12.0.jar;C:/Users/timot/.m2/repository/org/slf4j/slf4j-api/1.8.0-beta4/slf4j-api-1.8.0-beta4.jar;C:/Users/timot/.m2/repository/com/eclipsesource/minimal-json/minimal-json/0.9.5/minimal-json-0.9.5.jar;C:/Users/timot/.m2/repository/com/github/Valorless/HavenBags/1.39.2/HavenBags-1.39.2.jar;C:/Users/timot/.m2/repository/com/mysql/mysql-connector-j/8.2.0/mysql-connector-j-8.2.0.jar;C:/Users/timot/.m2/repository/com/google/protobuf/protobuf-java/3.21.9/protobuf-java-3.21.9.jar;C:/Users/timot/.m2/repository/org/xerial/sqlite-jdbc/3.42.0.0/sqlite-jdbc-3.42.0.0.jar' +-encoding +'UTF-8' +-protected +-quiet +-sourcepath +'C:/Users/timot/eclipse-workspace/CustomRecipesAPI/src' +-author +-bottom +'Copyright © 2025. All rights reserved.' +-charset +'UTF-8' +-d +'C:/Users/timot/eclipse-workspace/CustomRecipesAPI/target/site/apidocs' +-docencoding +'UTF-8' +-doctitle +'CustomRecipes 2.0.5.4 API' +-linkoffline +'https://docs.oracle.com/javase/8/docs/api' 'C:/Users/timot/eclipse-workspace/CustomRecipesAPI/target/javadoc-bundle-options' +-use +-version +-windowtitle +'CustomRecipes 2.0.5.4 API' diff --git a/docs/overview-summary.html b/docs/overview-summary.html new file mode 100644 index 0000000..328266f --- /dev/null +++ b/docs/overview-summary.html @@ -0,0 +1,26 @@ + + + + +CustomRecipes 2.0.5.4 API + + + + + + + + + + + +
+ +

index.html

+
+ + diff --git a/docs/overview-tree.html b/docs/overview-tree.html new file mode 100644 index 0000000..bbd61cd --- /dev/null +++ b/docs/overview-tree.html @@ -0,0 +1,156 @@ + + + + +Class Hierarchy (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
+ +
+
+
+
+

Hierarchy For All Packages

+
+Package Hierarchies: + +
+

Class Hierarchy

+ +
+
+

Enum Class Hierarchy

+ +
+
+
+ +
+
+
+ + diff --git a/docs/package-search-index.js b/docs/package-search-index.js new file mode 100644 index 0000000..d4f0995 --- /dev/null +++ b/docs/package-search-index.js @@ -0,0 +1 @@ +packageSearchIndex = [{"l":"All Packages","u":"allpackages-index.html"},{"l":"me.mehboss.brewing"},{"l":"me.mehboss.commands"},{"l":"me.mehboss.recipe"},{"l":"me.mehboss.utils"},{"l":"me.mehboss.utils.data"}];updateSearchResults(); \ No newline at end of file diff --git a/docs/packages b/docs/packages new file mode 100644 index 0000000..e459237 --- /dev/null +++ b/docs/packages @@ -0,0 +1,5 @@ +me.mehboss.brewing +me.mehboss.commands +me.mehboss.recipe +me.mehboss.utils +me.mehboss.utils.data \ No newline at end of file diff --git a/docs/resource-files/copy.svg b/docs/resource-files/copy.svg new file mode 100644 index 0000000..b07ac7e --- /dev/null +++ b/docs/resource-files/copy.svg @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/docs/resource-files/fonts/DejaVuLGCSans-Bold.woff b/docs/resource-files/fonts/DejaVuLGCSans-Bold.woff new file mode 100644 index 0000000..63a79c0 Binary files /dev/null and b/docs/resource-files/fonts/DejaVuLGCSans-Bold.woff differ diff --git a/docs/resource-files/fonts/DejaVuLGCSans-Bold.woff2 b/docs/resource-files/fonts/DejaVuLGCSans-Bold.woff2 new file mode 100644 index 0000000..0665554 Binary files /dev/null and b/docs/resource-files/fonts/DejaVuLGCSans-Bold.woff2 differ diff --git a/docs/resource-files/fonts/DejaVuLGCSans-BoldOblique.woff b/docs/resource-files/fonts/DejaVuLGCSans-BoldOblique.woff new file mode 100644 index 0000000..dead290 Binary files /dev/null and b/docs/resource-files/fonts/DejaVuLGCSans-BoldOblique.woff differ diff --git a/docs/resource-files/fonts/DejaVuLGCSans-BoldOblique.woff2 b/docs/resource-files/fonts/DejaVuLGCSans-BoldOblique.woff2 new file mode 100644 index 0000000..89328f3 Binary files /dev/null and b/docs/resource-files/fonts/DejaVuLGCSans-BoldOblique.woff2 differ diff --git a/docs/resource-files/fonts/DejaVuLGCSans-Oblique.woff b/docs/resource-files/fonts/DejaVuLGCSans-Oblique.woff new file mode 100644 index 0000000..6c62443 Binary files /dev/null and b/docs/resource-files/fonts/DejaVuLGCSans-Oblique.woff differ diff --git a/docs/resource-files/fonts/DejaVuLGCSans-Oblique.woff2 b/docs/resource-files/fonts/DejaVuLGCSans-Oblique.woff2 new file mode 100644 index 0000000..8a50fa1 Binary files /dev/null and b/docs/resource-files/fonts/DejaVuLGCSans-Oblique.woff2 differ diff --git a/docs/resource-files/fonts/DejaVuLGCSans.woff b/docs/resource-files/fonts/DejaVuLGCSans.woff new file mode 100644 index 0000000..a0f1efa Binary files /dev/null and b/docs/resource-files/fonts/DejaVuLGCSans.woff differ diff --git a/docs/resource-files/fonts/DejaVuLGCSans.woff2 b/docs/resource-files/fonts/DejaVuLGCSans.woff2 new file mode 100644 index 0000000..fecdbd8 Binary files /dev/null and b/docs/resource-files/fonts/DejaVuLGCSans.woff2 differ diff --git a/docs/resource-files/fonts/DejaVuLGCSansMono-Bold.woff b/docs/resource-files/fonts/DejaVuLGCSansMono-Bold.woff new file mode 100644 index 0000000..9f646ba Binary files /dev/null and b/docs/resource-files/fonts/DejaVuLGCSansMono-Bold.woff differ diff --git a/docs/resource-files/fonts/DejaVuLGCSansMono-Bold.woff2 b/docs/resource-files/fonts/DejaVuLGCSansMono-Bold.woff2 new file mode 100644 index 0000000..346de2d Binary files /dev/null and b/docs/resource-files/fonts/DejaVuLGCSansMono-Bold.woff2 differ diff --git a/docs/resource-files/fonts/DejaVuLGCSansMono-BoldOblique.woff b/docs/resource-files/fonts/DejaVuLGCSansMono-BoldOblique.woff new file mode 100644 index 0000000..7a6b3ac Binary files /dev/null and b/docs/resource-files/fonts/DejaVuLGCSansMono-BoldOblique.woff differ diff --git a/docs/resource-files/fonts/DejaVuLGCSansMono-BoldOblique.woff2 b/docs/resource-files/fonts/DejaVuLGCSansMono-BoldOblique.woff2 new file mode 100644 index 0000000..ede24ef Binary files /dev/null and b/docs/resource-files/fonts/DejaVuLGCSansMono-BoldOblique.woff2 differ diff --git a/docs/resource-files/fonts/DejaVuLGCSansMono-Oblique.woff b/docs/resource-files/fonts/DejaVuLGCSansMono-Oblique.woff new file mode 100644 index 0000000..892833b Binary files /dev/null and b/docs/resource-files/fonts/DejaVuLGCSansMono-Oblique.woff differ diff --git a/docs/resource-files/fonts/DejaVuLGCSansMono-Oblique.woff2 b/docs/resource-files/fonts/DejaVuLGCSansMono-Oblique.woff2 new file mode 100644 index 0000000..9e90514 Binary files /dev/null and b/docs/resource-files/fonts/DejaVuLGCSansMono-Oblique.woff2 differ diff --git a/docs/resource-files/fonts/DejaVuLGCSansMono.woff b/docs/resource-files/fonts/DejaVuLGCSansMono.woff new file mode 100644 index 0000000..e94e844 Binary files /dev/null and b/docs/resource-files/fonts/DejaVuLGCSansMono.woff differ diff --git a/docs/resource-files/fonts/DejaVuLGCSansMono.woff2 b/docs/resource-files/fonts/DejaVuLGCSansMono.woff2 new file mode 100644 index 0000000..9460c05 Binary files /dev/null and b/docs/resource-files/fonts/DejaVuLGCSansMono.woff2 differ diff --git a/docs/resource-files/fonts/DejaVuLGCSerif-Bold.woff b/docs/resource-files/fonts/DejaVuLGCSerif-Bold.woff new file mode 100644 index 0000000..0f38846 Binary files /dev/null and b/docs/resource-files/fonts/DejaVuLGCSerif-Bold.woff differ diff --git a/docs/resource-files/fonts/DejaVuLGCSerif-Bold.woff2 b/docs/resource-files/fonts/DejaVuLGCSerif-Bold.woff2 new file mode 100644 index 0000000..d65a5e9 Binary files /dev/null and b/docs/resource-files/fonts/DejaVuLGCSerif-Bold.woff2 differ diff --git a/docs/resource-files/fonts/DejaVuLGCSerif-BoldItalic.woff b/docs/resource-files/fonts/DejaVuLGCSerif-BoldItalic.woff new file mode 100644 index 0000000..63fd5e3 Binary files /dev/null and b/docs/resource-files/fonts/DejaVuLGCSerif-BoldItalic.woff differ diff --git a/docs/resource-files/fonts/DejaVuLGCSerif-BoldItalic.woff2 b/docs/resource-files/fonts/DejaVuLGCSerif-BoldItalic.woff2 new file mode 100644 index 0000000..e29307e Binary files /dev/null and b/docs/resource-files/fonts/DejaVuLGCSerif-BoldItalic.woff2 differ diff --git a/docs/resource-files/fonts/DejaVuLGCSerif-Italic.woff b/docs/resource-files/fonts/DejaVuLGCSerif-Italic.woff new file mode 100644 index 0000000..5df6d00 Binary files /dev/null and b/docs/resource-files/fonts/DejaVuLGCSerif-Italic.woff differ diff --git a/docs/resource-files/fonts/DejaVuLGCSerif-Italic.woff2 b/docs/resource-files/fonts/DejaVuLGCSerif-Italic.woff2 new file mode 100644 index 0000000..61ff146 Binary files /dev/null and b/docs/resource-files/fonts/DejaVuLGCSerif-Italic.woff2 differ diff --git a/docs/resource-files/fonts/DejaVuLGCSerif.woff b/docs/resource-files/fonts/DejaVuLGCSerif.woff new file mode 100644 index 0000000..280e378 Binary files /dev/null and b/docs/resource-files/fonts/DejaVuLGCSerif.woff differ diff --git a/docs/resource-files/fonts/DejaVuLGCSerif.woff2 b/docs/resource-files/fonts/DejaVuLGCSerif.woff2 new file mode 100644 index 0000000..bbd32cd Binary files /dev/null and b/docs/resource-files/fonts/DejaVuLGCSerif.woff2 differ diff --git a/docs/resource-files/fonts/dejavu.css b/docs/resource-files/fonts/dejavu.css new file mode 100644 index 0000000..b450caa --- /dev/null +++ b/docs/resource-files/fonts/dejavu.css @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ + */ + +/* DejaVu fonts v2.37 */ + +@font-face { + font-family: 'DejaVu Sans Mono'; + src: url('DejaVuLGCSansMono.woff2') format('woff2'), + url('DejaVuLGCSansMono.woff') format('woff'); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: 'DejaVu Sans Mono'; + src: url('DejaVuLGCSansMono-Oblique.woff2') format('woff2'), + url('DejaVuLGCSansMono-Oblique.woff') format('woff'); + font-weight: normal; + font-style: italic; +} + +@font-face { + font-family: 'DejaVu Sans Mono'; + src: url('DejaVuLGCSansMono-Bold.woff2') format('woff2'), + url('DejaVuLGCSansMono-Bold.woff') format('woff'); + font-weight: bold; + font-style: normal; +} + +@font-face { + font-family: 'DejaVu Sans Mono'; + src: url('DejaVuLGCSansMono-BoldOblique.woff2') format('woff2'), + url('DejaVuLGCSansMono-BoldOblique.woff') format('woff'); + font-weight: bold; + font-style: italic; +} + +@font-face { + font-family: 'DejaVu Sans'; + src: url('DejaVuLGCSans.woff2') format('woff2'), + url('DejaVuLGCSans.woff') format('woff'); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: 'DejaVu Sans'; + src: url('DejaVuLGCSans-Oblique.woff2') format('woff2'), + url('DejaVuLGCSans-Oblique.woff') format('woff'); + font-weight: normal; + font-style: italic; +} + +@font-face { + font-family: 'DejaVu Sans'; + src: url('DejaVuLGCSans-Bold.woff2') format('woff2'), + url('DejaVuLGCSans-Bold.woff') format('woff'); + font-weight: bold; + font-style: normal; +} + +@font-face { + font-family: 'DejaVu Sans'; + src: url('DejaVuLGCSans-BoldOblique.woff2') format('woff2'), + url('DejaVuLGCSans-BoldOblique.woff') format('woff'); + font-weight: bold; + font-style: italic; +} + +@font-face { + font-family: 'DejaVu Serif'; + src: url('DejaVuLGCSerif.woff2') format('woff2'), + url('DejaVuLGCSerif.woff') format('woff'); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: 'DejaVu Serif'; + src: url('DejaVuLGCSerif-Italic.woff2') format('woff2'), + url('DejaVuLGCSerif-Italic.woff') format('woff'); + font-weight: normal; + font-style: italic; +} + +@font-face { + font-family: 'DejaVu Serif'; + src: url('DejaVuLGCSerif-Bold.woff2') format('woff2'), + url('DejaVuLGCSerif-Bold.woff') format('woff'); + font-weight: bold; + font-style: normal; +} + +@font-face { + font-family: 'DejaVu Serif'; + src: url('DejaVuLGCSerif-BoldItalic.woff2') format('woff2'), + url('DejaVuLGCSerif-BoldItalic.woff') format('woff'); + font-weight: bold; + font-style: italic; +} diff --git a/docs/resource-files/glass.png b/docs/resource-files/glass.png new file mode 100644 index 0000000..a7f591f Binary files /dev/null and b/docs/resource-files/glass.png differ diff --git a/docs/resource-files/jquery-ui.min.css b/docs/resource-files/jquery-ui.min.css new file mode 100644 index 0000000..693327f --- /dev/null +++ b/docs/resource-files/jquery-ui.min.css @@ -0,0 +1,6 @@ +/*! jQuery UI - v1.13.2 - 2023-02-27 +* http://jqueryui.com +* Includes: core.css, autocomplete.css, menu.css +* Copyright jQuery Foundation and other contributors; Licensed MIT */ + +.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;-ms-filter:"alpha(opacity=0)"}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important;pointer-events:none}.ui-icon{display:inline-block;vertical-align:middle;margin-top:-.25em;position:relative;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-icon-block{left:50%;margin-left:-8px;display:block}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-autocomplete{position:absolute;top:0;left:0;cursor:default}.ui-menu{list-style:none;padding:0;margin:0;display:block;outline:0}.ui-menu .ui-menu{position:absolute}.ui-menu .ui-menu-item{margin:0;cursor:pointer;list-style-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7")}.ui-menu .ui-menu-item-wrapper{position:relative;padding:3px 1em 3px .4em}.ui-menu .ui-menu-divider{margin:5px 0;height:0;font-size:0;line-height:0;border-width:1px 0 0 0}.ui-menu .ui-state-focus,.ui-menu .ui-state-active{margin:-1px}.ui-menu-icons{position:relative}.ui-menu-icons .ui-menu-item-wrapper{padding-left:2em}.ui-menu .ui-icon{position:absolute;top:0;bottom:0;left:.2em;margin:auto 0}.ui-menu .ui-menu-icon{left:auto;right:0} \ No newline at end of file diff --git a/docs/resource-files/link.svg b/docs/resource-files/link.svg new file mode 100644 index 0000000..2bbac38 --- /dev/null +++ b/docs/resource-files/link.svg @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/docs/resource-files/stylesheet.css b/docs/resource-files/stylesheet.css new file mode 100644 index 0000000..d177678 --- /dev/null +++ b/docs/resource-files/stylesheet.css @@ -0,0 +1,1461 @@ +/* + * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ + */ + +/* + * Javadoc style sheet + */ + +@import url('fonts/dejavu.css'); + +/* + * These CSS custom properties (variables) define the core color and font + * properties used in this stylesheet. + */ +:root { + /* body, block and code fonts */ + --body-font-family: 'DejaVu Sans', Arial, Helvetica, sans-serif; + --block-font-family: 'DejaVu Serif', Georgia, "Times New Roman", Times, serif; + --code-font-family: 'DejaVu Sans Mono', monospace; + /* Base font sizes for body and code elements */ + --body-font-size: 14px; + --block-font-size: 14px; + --code-font-size: 14px; + --nav-font-size: 13.2px; + /* Line height for continuous text blocks */ + --block-line-height: 1.4em; + /* Text colors for body and block elements */ + --body-text-color: #353833; + --block-text-color: #474747; + /* Background colors for various structural elements */ + --body-background-color: #ffffff; + --section-background-color: #f8f8f8; + --detail-background-color: #ffffff; + /* Colors for navigation bar and table captions */ + --navbar-background-color: #4D7A97; + --navbar-text-color: #ffffff; + /* Background color for subnavigation and various headers */ + --subnav-background-color: #dee3e9; + /* Background and text colors for selected tabs and navigation items */ + --selected-background-color: #f8981d; + --selected-text-color: #253441; + --selected-link-color: #1f389c; + /* Background colors for generated tables */ + --even-row-color: #ffffff; + --odd-row-color: #eeeeef; + /* Text color for page title */ + --title-color: #2c4557; + /* Text colors for links */ + --link-color: #4A6782; + --link-color-active: #bb7a2a; + /* Snippet colors */ + --snippet-background-color: #ebecee; + --snippet-text-color: var(--block-text-color); + --snippet-highlight-color: #f7c590; + /* Border colors for structural elements and user defined tables */ + --border-color: #ededed; + --table-border-color: #000000; + /* Search input colors */ + --search-input-background-color: #ffffff; + --search-input-text-color: #000000; + --search-input-placeholder-color: #909090; + /* Highlight color for active search tag target */ + --search-tag-highlight-color: #ffff00; + /* Adjustments for icon and active background colors of copy-to-clipboard buttons */ + --copy-icon-brightness: 100%; + --copy-button-background-color-active: rgba(168, 168, 176, 0.3); + /* Colors for invalid tag notifications */ + --invalid-tag-background-color: #ffe6e6; + --invalid-tag-text-color: #000000; + /* Navigation bar dimensions */ + --top-nav-height: 44px; + --sub-nav-height: 34px; + --nav-height: calc(var(--top-nav-height) + var(--sub-nav-height)); + scroll-behavior: smooth; +} +/* + * Styles for individual HTML elements. + * + * These are styles that are specific to individual HTML elements. Changing them affects the style of a particular + * HTML element throughout the page. + */ +body { + background-color:var(--body-background-color); + color:var(--body-text-color); + font-family:var(--body-font-family); + font-size:var(--body-font-size); + margin:0; + padding:0; + height:100%; + width:100%; +} +main [id] { + scroll-margin-top: calc(var(--nav-height) + 6px); +} +a:link, a:visited { + text-decoration:none; + color:var(--link-color); +} +a[href]:hover, a[href]:active { + text-decoration:none; + color:var(--link-color-active); +} +pre { + font-family:var(--code-font-family); + font-size:var(--code-font-size); +} +h1 { + font-size:1.428em; +} +h2 { + font-size:1.285em; +} +h3 { + font-size:1.14em; +} +h4 { + font-size:1.072em; +} +h5 { + font-size:1.001em; +} +h6 { + font-size:0.93em; +} +/* Disable font boosting for selected elements */ +h1, h2, h3, h4, h5, h6, div.member-signature { + max-height: 1000em; +} +ul { + list-style-type:disc; +} +code, tt { + font-family:var(--code-font-family); +} +:not(h1, h2, h3, h4, h5, h6) > code, +:not(h1, h2, h3, h4, h5, h6) > tt { + font-size:var(--code-font-size); + line-height:1.4em; +} +dt code { + font-family:var(--code-font-family); + font-size:1em; + padding-top:4px; +} +.summary-table dt code { + font-family:var(--code-font-family); + font-size:1em; + vertical-align:top; + padding-top:4px; +} +sup { + font-size:8px; +} +button { + font-family: var(--body-font-family); + font-size: 1em; +} +hr { + border-color: #aaa; +} +/* + * Styles for HTML generated by javadoc. + * + * These are style classes that are used by the standard doclet to generate HTML documentation. + */ + +/* + * Styles for document title and copyright. + */ +.about-language { + flex: 0 0 auto; + padding:0 20px; + margin:0; + font-size:0.915em; + max-width: 50%; + white-space: nowrap; +} +.legal-copy { +} +/* + * Styles for navigation bar. + */ +@media screen { + header { + position:sticky; + top:0; + z-index:2; + background: var(--body-background-color); + } +} +.nav-content { + display:flex; + flex-direction: row; + align-items: center; + width: 100%; + height: 100%; +} +.top-nav { + background-color:var(--navbar-background-color); + color:var(--navbar-text-color); + width:100%; + height:var(--top-nav-height); + overflow:visible; + font-size:0.857em; + position:relative; +} +.top-nav nav.toc { + display: none; + flex-direction: column; +} +.top-nav nav.toc button.show-sidebar, +.top-nav nav.toc button.hide-sidebar { + display: none; +} +button#navbar-toggle-button { + display:none; +} +ul.nav-list { + display:inline-flex; + margin:0; + padding-left:4px; + flex: 1 1 auto; + white-space: nowrap; +} +ul.nav-list li { + list-style:none; + padding: 5px 6px; + text-transform:uppercase; + height: 1.2em; +} +div.sub-nav { + background-color:var(--subnav-background-color); + width:100%; + overflow:hidden; + font-size:var(--nav-font-size); + height: var(--sub-nav-height); +} +ol.sub-nav-list { + flex: 1 1 90%; + line-height: 1.8em; + display: inline-flex; + overflow: auto; + scroll-snap-type: x mandatory; + scroll-padding-left: 13px; + scrollbar-width: none; + padding-left:6px; + white-space: nowrap; + margin:0; +} +ol.sub-nav-list::-webkit-scrollbar { + display: none; +} +ol.sub-nav-list li { + list-style:none; + scroll-snap-align: start; +} +ol.sub-nav-list li:not(:first-child) { + list-style-type: " > "; + margin-left: 20px; +} +ol.sub-nav-list a { + padding: 3px; +} +ol.sub-nav-list a.current-selection { + background-color: var(--section-background-color); + border-radius: 4px; +} +.sub-nav .nav-list-search { + flex: 1 1 10%; + margin:0; + padding:6px; + position:relative; + white-space: nowrap; +} +.top-nav .nav-list a:link, .top-nav .nav-list a:active, .top-nav .nav-list a:visited { + color:var(--navbar-text-color); + text-decoration:none; + text-transform:uppercase; +} +.top-nav .nav-list a:hover { + color:var(--link-color-active); +} +.nav-bar-cell1-rev { + background-color:var(--selected-background-color); + color:var(--selected-text-color); + margin: 0 5px; +} +.skip-nav { + position:absolute; + top:auto; + left:-9999px; + overflow:hidden; +} +/* + * Hide navigation links and search box in print layout + */ +@media print { + ul.nav-list, div.sub-nav { + display:none; + } +} +/* + * Styles for page header. + */ +.title { + color:var(--title-color); + margin:10px 0; +} +.sub-title { + margin:5px 0 0; +} +ul.contents-list { + margin: 0 0 15px; + padding: 0; + list-style: none; +} +ul.contents-list li { + font-size:0.93em; +} +/* + * Styles for headings. + */ +body.class-declaration-page .summary h2, +body.class-declaration-page .details h2, +body.class-use-page h2, +body.module-declaration-page .block-list h2 { + font-style: italic; + padding:0; + margin:15px 0; + overflow-x:auto; +} +body.class-declaration-page .summary h3, +body.class-declaration-page .details h3 { + background-color:var(--subnav-background-color); + border:1px solid var(--border-color); + margin:0 0 6px -8px; + padding:7px 5px; + overflow-x:auto; +} +/* + * Styles for page layout containers. + */ +.main-grid { + display: flex; + flex-direction: row; +} +.main-grid main { + flex: 2.6 1 0; + min-width: 240px +} +.main-grid nav.toc { + flex: 1 1 0; + min-width: 240px; +} +main { + clear:both; + padding:10px 20px; + position:relative; +} +section[id$=-description] :is(dl, ol, ul, p, div, blockquote, pre):last-child, +section[id$=-description] :is(dl, ol, ul):last-child > :is(li, dd):last-child { + margin-bottom:4px; +} +dl.notes > dt { + font-family: var(--body-font-family); + font-size:0.856em; + font-weight:bold; + margin:10px 0 0; + color:var(--body-text-color); +} +dl.notes > dd { + margin:5px 10px 10px 15px; + font-size:var(--block-font-size); + font-family:var(--block-font-family); +} +dl.notes > dd > ul, dl.notes > dd > ol { + margin-bottom: 1em; + margin-top: 1em; +} +dl.name-value > dt { + margin-left:1px; + font-size:1.1em; + display:inline; + font-weight:bold; +} +dl.name-value > dd { + margin:0 0 0 1px; + font-size:1.1em; + display:inline; +} +/* + * Styles for table of contents. + */ +.main-grid nav.toc { + background-color: var(--section-background-color); + border-right: 1px solid var(--border-color); + position: sticky; + top: calc(var(--nav-height)); + max-height: calc(100vh - var(--nav-height)); + display: flex; + flex-direction: column; + font-family: var(--body-font-family); + z-index: 1; +} +.main-grid nav.toc div.toc-header { + background-color: var(--section-background-color); + border-right: 1px solid var(--border-color); + top: var(--nav-height); + z-index: 1; + padding: 15px 20px; +} +.main-grid nav.toc > ol.toc-list { + max-height: calc(100vh - var(--nav-height) - 100px); + padding-left: 12px; +} +.main-grid nav.toc button { + position: absolute; + bottom: 16px; + z-index: 3; + background-color: var(--section-background-color); + color: #666666; + font-size: 0.76rem; + border: none; + cursor: pointer; + padding: 6px 10px; + white-space: nowrap; +} +.main-grid nav.toc button.hide-sidebar { + right: 0; +} +.main-grid nav.toc button.show-sidebar { + left: 0; + display: none; +} +.main-grid nav.toc button span { + display: none; +} +.main-grid nav.toc button:hover { + color: var(--body-text-color); + border: 1px solid var(--subnav-background-color); +} +.main-grid nav.toc button:active { + background-color: var(--subnav-background-color); + color: var(--link-color-active); +} +.main-grid nav.toc button:hover span, +.main-grid nav.toc button:active span { + display: inline; +} +.main-grid nav.toc button:hover { + box-shadow: 1px 1px 5px rgba(0,0,0,0.2); +} +.main-grid nav.toc.hide-sidebar { + min-width: revert; + max-width: 28px; +} +.main-grid nav.toc.hide-sidebar div.toc-header, +.main-grid nav.toc.hide-sidebar ol.toc-list, +.main-grid nav.toc.hide-sidebar button.hide-sidebar { + display: none; +} +.main-grid nav.toc.hide-sidebar button.show-sidebar { + display: inline; +} +nav.toc div.toc-header { + padding: 15px; + display: inline-flex; + align-items: center; + color: var(--body-text-color); + background-color: var(--body-background-color); + font-size: 0.856em; + font-weight: bold; + white-space: nowrap; + overflow-x: hidden; + position: sticky; + min-height: 20px; +} +nav.toc > ol.toc-list { + overflow: hidden auto; + overscroll-behavior: contain; +} +nav.toc ol.toc-list { + list-style: none; + padding-left: 8px; + margin: 0; +} +nav.toc ol.toc-list ol.toc-list { + margin-left: 8px; +} +nav.toc ol.toc-list li { + margin: 0; + font-size: var(--nav-font-size); + overflow-x: hidden; +} +a.current-selection { + font-weight: bold; +} +nav.toc a { + display: block; + padding: 8px; +} +nav.toc a.current-selection { + background-color: var(--subnav-background-color); +} +/* + * Styles for lists. + */ +li.circle { + list-style:circle; +} +ul.horizontal li { + display:inline; + font-size:0.9em; +} +div.inheritance { + margin:0; + padding:0; +} +div.inheritance div.inheritance { + margin-left:2em; +} +main > div.inheritance { + overflow-x:auto; +} +ul.block-list, +ul.details-list, +ul.member-list, +ul.summary-list { + margin:4px 0 10px; + padding:0; +} +ul.block-list > li, +ul.details-list > li, +ul.member-list > li, +ul.summary-list > li { + list-style:none; + margin-bottom:15px; + line-height:1.4; +} +ul.ref-list { + padding:0; + margin:0; +} +ul.ref-list > li { + list-style:none; +} +.summary-table dl, .summary-table dl dt, .summary-table dl dd { + margin-top:0; + margin-bottom:1px; +} +dl.notes > dd > ul.tag-list, dl.notes > dd > ul.tag-list-long { + padding-left: 0; + margin: 0; + list-style: none; +} +ul.tag-list li { + display: inline; +} +ul.tag-list li:not(:last-child):after, +ul.tag-list-long li:not(:last-child):after +{ + content: ", "; + white-space: pre-wrap; +} +ul.preview-feature-list { + list-style: none; + margin:0; + padding:0.1em; + line-height: 1.6em; +} +/* + * Styles for tables. + */ +.summary-table, .details-table { + width:100%; + border-spacing:0; + border:1px solid var(--border-color); + border-top:0; + padding:0; +} +.caption { + position:relative; + text-align:left; + background-repeat:no-repeat; + color:var(--selected-text-color); + clear:none; + overflow:hidden; + padding: 10px 0 0 1px; + margin:0; +} +.caption a:link, .caption a:visited { + color:var(--selected-link-color); +} +.caption a:hover, +.caption a:active { + color:var(--navbar-text-color); +} +.caption span { + font-weight:bold; + white-space:nowrap; + padding:5px 12px 7px; + display:inline-block; + float:left; + background-color:var(--selected-background-color); + border: none; + height:16px; +} +div.table-tabs { + padding: 10px 0 0 1px; + margin: 0; +} +div.table-tabs > button { + border: none; + cursor: pointer; + padding: 5px 12px 7px; + font-weight: bold; + margin-right: 8px; +} +div.table-tabs > .active-table-tab { + background: var(--selected-background-color); + color: var(--selected-text-color); +} +div.table-tabs > button.table-tab { + background: var(--navbar-background-color); + color: var(--navbar-text-color); +} +.two-column-search-results { + display: grid; + grid-template-columns: minmax(400px, max-content) minmax(400px, auto); +} +div.checkboxes { + line-height: 2em; +} +div.checkboxes > span { + margin-left: 10px; +} +div.checkboxes > label { + margin-left: 8px; + white-space: nowrap; +} +div.checkboxes > label > input { + margin: 0 2px; +} +.two-column-summary { + display: grid; + grid-template-columns: minmax(25%, max-content) minmax(25%, auto); +} +.three-column-summary { + display: grid; + grid-template-columns: minmax(15%, max-content) minmax(20%, max-content) minmax(20%, auto); +} +.three-column-release-summary { + display: grid; + grid-template-columns: minmax(40%, max-content) minmax(10%, max-content) minmax(40%, auto); +} +.four-column-summary { + display: grid; + grid-template-columns: minmax(10%, max-content) minmax(15%, max-content) minmax(15%, max-content) minmax(15%, auto); +} +@media screen and (max-width: 1000px) { + .four-column-summary { + display: grid; + grid-template-columns: minmax(15%, max-content) minmax(15%, auto); + } +} +@media screen and (max-width: 800px) { + .two-column-search-results { + display: grid; + grid-template-columns: minmax(40%, max-content) minmax(40%, auto); + } + .three-column-summary { + display: grid; + grid-template-columns: minmax(10%, max-content) minmax(25%, auto); + } + .three-column-release-summary { + display: grid; + grid-template-columns: minmax(70%, max-content) minmax(30%, max-content) + } + .three-column-summary .col-last, + .three-column-release-summary .col-last{ + grid-column-end: span 2; + } +} +@media screen and (max-width: 600px) { + .two-column-summary { + display: grid; + grid-template-columns: 1fr; + } +} +.summary-table > div, .details-table > div { + text-align:left; + padding: 8px 3px 3px 7px; + overflow: auto hidden; + scrollbar-width: thin; +} +.col-first, .col-second, .col-last, .col-constructor-name, .col-summary-item-name { + vertical-align:top; + padding-right:0; + padding-top:8px; + padding-bottom:3px; +} +.table-header { + background:var(--subnav-background-color); + font-weight: bold; +} +/* Sortable table columns */ +.table-header[onclick] { + cursor: pointer; +} +.table-header[onclick]::after { + content:""; + display:inline-block; + background-image:url('data:image/svg+xml; utf8, \ + \ + '); + background-size:100% 100%; + width:9px; + height:14px; + margin-left:4px; + margin-bottom:-3px; +} +.table-header[onclick].sort-asc::after { + background-image:url('data:image/svg+xml; utf8, \ + \ + \ + '); + +} +.table-header[onclick].sort-desc::after { + background-image:url('data:image/svg+xml; utf8, \ + \ + \ + '); +} +.col-first, .col-first { + font-size:0.93em; +} +.col-second, .col-second, .col-last, .col-constructor-name, .col-summary-item-name, .col-last { + font-size:0.93em; +} +.col-first, .col-second, .col-constructor-name { + vertical-align:top; + overflow: auto; +} +.col-last { + white-space:normal; +} +.col-first a:link, .col-first a:visited, +.col-second a:link, .col-second a:visited, +.col-first a:link, .col-first a:visited, +.col-second a:link, .col-second a:visited, +.col-constructor-name a:link, .col-constructor-name a:visited, +.col-summary-item-name a:link, .col-summary-item-name a:visited { + font-weight:bold; +} +.even-row-color, .even-row-color .table-header { + background-color:var(--even-row-color); +} +.odd-row-color, .odd-row-color .table-header { + background-color:var(--odd-row-color); +} +/* + * Styles for contents. + */ +div.block { + font-size:var(--block-font-size); + font-family:var(--block-font-family); + line-height:var(--block-line-height); +} +.col-last div { + padding-top:0; +} +.col-last a { + padding-bottom:3px; +} +.module-signature, +.package-signature, +.type-signature, +.member-signature { + font-family:var(--code-font-family); + font-size:1em; + margin:8px 0 14px; + white-space: pre-wrap; +} +.module-signature, +.package-signature, +.type-signature { + margin-top: 0; +} +.member-signature .type-parameters-long, +.member-signature .parameters, +.member-signature .exceptions { + display: inline-block; + vertical-align: top; + white-space: pre; +} +.member-signature .type-parameters { + white-space: normal; +} +/* + * Styles for formatting effect. + */ +.source-line-no { + /* Color of line numbers in source pages can be set via custom property below */ + color:var(--source-linenumber-color, green); + padding:0 30px 0 0; +} +.block { + display:block; + margin:0 10px 5px 0; + color:var(--block-text-color); +} +.deprecated-label, .description-from-type-label, .implementation-label, .member-name-link, +.package-hierarchy-label, .type-name-label, .type-name-link, .search-tag-link, .preview-label, .restricted-label { + font-weight:bold; +} +.deprecation-comment, .help-footnote, .preview-comment, .restricted-comment { + font-style:italic; +} +.deprecation-block, .preview-block, .restricted-block { + font-size:1em; + font-family:var(--block-font-family); + border-style:solid; + border-width:thin; + border-radius:10px; + padding:10px; + margin-bottom:10px; + margin-right:10px; + display:inline-block; +} +div.block div.deprecation-comment { + font-style:normal; +} +details.invalid-tag, span.invalid-tag { + font-size:1em; + font-family:var(--block-font-family); + color: var(--invalid-tag-text-color); + background: var(--invalid-tag-background-color); + border: thin solid var(--table-border-color); + border-radius:2px; + padding: 2px 4px; + display:inline-block; +} +details summary { + cursor: pointer; +} +/* + * Styles specific to HTML5 elements. + */ +main, nav, header, footer, section { + display:block; +} +/* + * Styles for javadoc search. + */ +.ui-menu .ui-state-active { + /* Overrides the color of selection used in jQuery UI */ + background: var(--selected-background-color); + color: var(--selected-text-color); + /* Workaround for browser bug, see JDK-8275889 */ + margin: -1px 0; + border-top: 1px solid var(--selected-background-color); + border-bottom: 1px solid var(--selected-background-color); +} +.ui-autocomplete-category { + font-weight:bold; + font-size:15px; + padding:7px 0 7px 3px; + background-color:var(--navbar-background-color); + color:var(--navbar-text-color); + box-sizing: border-box; +} +.ui-autocomplete { + max-height:85%; + max-width:65%; + overflow:auto; + white-space:nowrap; + box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23); + overscroll-behavior: contain; +} +ul.ui-autocomplete { + position:fixed; + z-index:10; + background-color: var(--body-background-color); +} +ul.ui-autocomplete li { + float:left; + clear:both; + min-width:100%; + box-sizing: border-box; +} +ul.ui-autocomplete li.ui-static-link { + position:sticky; + bottom:0; + left:0; + background: var(--subnav-background-color); + padding: 5px 0; + font-family: var(--body-font-family); + font-size: 0.93em; + font-weight: bolder; + z-index: 10; +} +li.ui-static-link a, li.ui-static-link a:visited { + text-decoration:none; + color:var(--link-color); + float:right; + margin-right:20px; +} +.ui-autocomplete .result-item { + font-size: inherit; +} +.ui-autocomplete .result-highlight { + font-weight:bold; +} +.ui-menu .ui-menu-item-wrapper { + padding-top: 0.4em; + padding-bottom: 0.4em; +} +.ui-menu .ui-menu-item-wrapper { + padding-top: 5px; + padding-bottom: 5px; +} +input[type="text"] { + background-image:url('glass.png'); + background-size:13px; + background-repeat:no-repeat; + background-position:2px 3px; + background-color: var(--search-input-background-color); + color: var(--search-input-text-color); + border-color: var(--border-color); + border-radius: 4px; + padding-left:20px; + font-size: var(--nav-font-size); + height: 17px; +} +input#search-input, input#page-search-input { + width: calc(180px + 10vw); + margin: 0; +} +input#search-input { + margin: 0 4px; + padding-right: 18px; + max-width: 340px; +} +input.filter-input { + width: 40%; + max-width: 140px; + margin: 0 4px; + padding-right: 18px; +} +input#reset-search, input.reset-filter { + background-color: transparent; + background-image:url('x.png'); + background-repeat:no-repeat; + background-size:contain; + border:0; + border-radius:0; + width:12px; + height:12px; + font-size:0; + display:none; +} +input#reset-search { + position:absolute; + right:15px; + top:11px; +} +input.reset-filter { + position: relative; + right: 20px; + top: 0; +} +input::placeholder { + color:var(--search-input-placeholder-color); + opacity: 1; +} +.search-tag-desc-result { + font-style:italic; + font-size:11px; +} +.search-tag-holder-result { + font-style:italic; + font-size:12px; +} +.search-tag-result:target { + background-color:var(--search-tag-highlight-color); +} +details.page-search-details { + display: inline-block; +} +div#result-container { + font-size: 1em; +} +div#result-container a.search-result-link { + padding: 0; + margin: 4px 0; + width: 100%; +} +#result-container .result-highlight { + font-weight:bolder; +} +.page-search-info { + background-color: var(--subnav-background-color); + border-radius: 3px; + border: 0 solid var(--border-color); + padding: 0 8px; + overflow: hidden; + height: 0; + transition: all 0.2s ease; +} +div.table-tabs > button.table-tab { + background: var(--navbar-background-color); + color: var(--navbar-text-color); +} +.page-search-header { + padding: 5px 12px 7px; + font-weight: bold; + margin-right: 3px; + background-color:var(--navbar-background-color); + color:var(--navbar-text-color); + display: inline-block; +} +button.page-search-header { + border: none; + cursor: pointer; +} +span#page-search-link { + text-decoration: underline; +} +.module-graph span, .sealed-graph span { + display:none; + position:absolute; +} +.module-graph:hover span, .sealed-graph:hover span { + display:block; + margin: -100px 0 0 100px; + z-index: 5; +} +.inherited-list { + margin: 10px 0; +} +.horizontal-scroll { + overflow: auto hidden; +} +section.class-description { + line-height: 1.4; +} +.summary section[class$="-summary"], .details section[class$="-details"], +.class-uses .detail, .serialized-class-details { + padding: 0 20px 5px 10px; + border: 1px solid var(--border-color); + background-color: var(--section-background-color); +} +.inherited-list, section[class$="-details"] .detail { + padding:0 0 5px 8px; + background-color:var(--detail-background-color); + border:none; +} +.vertical-separator { + padding: 0 5px; +} +.help-section { + font-size: var(--block-font-size); + line-height: var(--block-line-height); +} +ul.help-section-list { + margin: 0; +} +ul.help-subtoc > li { + display: inline-block; + padding-right: 5px; + font-size: smaller; +} +ul.help-subtoc > li::before { + content: "\2022" ; + padding-right:2px; +} +.help-note { + font-style: italic; +} +/* + * Indicator icon for external links. + */ +main a[href*="://"]::after { + content:""; + display:inline-block; + background-image:url('data:image/svg+xml; utf8, \ + \ + \ + '); + background-size:100% 100%; + width:7px; + height:7px; + margin-left:2px; + margin-bottom:4px; +} +main a[href*="://"]:hover::after, +main a[href*="://"]:focus::after { + background-image:url('data:image/svg+xml; utf8, \ + \ + \ + '); +} +/* + * Styles for header/section anchor links + */ +a.anchor-link { + opacity: 0; + transition: opacity 0.1s; +} +:hover > a.anchor-link { + opacity: 80%; +} +a.anchor-link:hover, +a.anchor-link:focus-visible, +a.anchor-link.visible { + opacity: 100%; +} +a.anchor-link > img { + width: 0.9em; + height: 0.9em; +} +/* + * Styles for copy-to-clipboard buttons + */ +button.copy { + opacity: 70%; + border: none; + border-radius: 3px; + position: relative; + background:none; + transition: opacity 0.3s; + cursor: pointer; +} +:hover > button.copy { + opacity: 80%; +} +button.copy:hover, +button.copy:active, +button.copy:focus-visible, +button.copy.visible { + opacity: 100%; +} +button.copy img { + position: relative; + background: none; + filter: brightness(var(--copy-icon-brightness)); +} +button.copy:active { + background-color: var(--copy-button-background-color-active); +} +button.copy span { + color: var(--body-text-color); + position: relative; + top: -0.1em; + transition: all 0.1s; + font-size: 0.76rem; + line-height: 1.2em; + opacity: 0; +} +button.copy:hover span, +button.copy:focus-visible span, +button.copy.visible span { + opacity: 100%; +} +/* search page copy button */ +button#page-search-copy { + margin-left: 0.4em; + padding:0.3em; + top:0.13em; +} +button#page-search-copy img { + width: 1.2em; + height: 1.2em; + padding: 0.01em 0; + top: 0.15em; +} +button#page-search-copy span { + color: var(--body-text-color); + line-height: 1.2em; + padding: 0.2em; + top: -0.18em; +} +div.page-search-info:hover button#page-search-copy span { + opacity: 100%; +} +/* snippet copy button */ +button.snippet-copy { + position: absolute; + top: 6px; + right: 6px; + height: 1.7em; + padding: 2px; +} +button.snippet-copy img { + width: 18px; + height: 18px; + padding: 0.05em 0; +} +button.snippet-copy span { + line-height: 1.2em; + padding: 0.2em; + position: relative; + top: -0.5em; +} +div.snippet-container:hover button.snippet-copy span { + opacity: 100%; +} +/* + * Styles for user-provided tables. + * + * borderless: + * No borders, vertical margins, styled caption. + * This style is provided for use with existing doc comments. + * In general, borderless tables should not be used for layout purposes. + * + * plain: + * Plain borders around table and cells, vertical margins, styled caption. + * Best for small tables or for complex tables for tables with cells that span + * rows and columns, when the "striped" style does not work well. + * + * striped: + * Borders around the table and vertical borders between cells, striped rows, + * vertical margins, styled caption. + * Best for tables that have a header row, and a body containing a series of simple rows. + */ + +table.borderless, +table.plain, +table.striped { + margin-top: 10px; + margin-bottom: 10px; +} +table.borderless > caption, +table.plain > caption, +table.striped > caption { + font-weight: bold; + font-size: smaller; +} +table.borderless th, table.borderless td, +table.plain th, table.plain td, +table.striped th, table.striped td { + padding: 2px 5px; +} +table.borderless, +table.borderless > thead > tr > th, table.borderless > tbody > tr > th, table.borderless > tr > th, +table.borderless > thead > tr > td, table.borderless > tbody > tr > td, table.borderless > tr > td { + border: none; +} +table.borderless > thead > tr, table.borderless > tbody > tr, table.borderless > tr { + background-color: transparent; +} +table.plain { + border-collapse: collapse; + border: 1px solid var(--table-border-color); +} +table.plain > thead > tr, table.plain > tbody tr, table.plain > tr { + background-color: transparent; +} +table.plain > thead > tr > th, table.plain > tbody > tr > th, table.plain > tr > th, +table.plain > thead > tr > td, table.plain > tbody > tr > td, table.plain > tr > td { + border: 1px solid var(--table-border-color); +} +table.striped { + border-collapse: collapse; + border: 1px solid var(--table-border-color); +} +table.striped > thead { + background-color: var(--subnav-background-color); +} +table.striped > thead > tr > th, table.striped > thead > tr > td { + border: 1px solid var(--table-border-color); +} +table.striped > tbody > tr:nth-child(even) { + background-color: var(--odd-row-color) +} +table.striped > tbody > tr:nth-child(odd) { + background-color: var(--even-row-color) +} +table.striped > tbody > tr > th, table.striped > tbody > tr > td { + border-left: 1px solid var(--table-border-color); + border-right: 1px solid var(--table-border-color); +} +table.striped > tbody > tr > th { + font-weight: normal; +} +/** + * Tweak style for small screens. + */ +@media screen and (max-width: 1050px) { + .summary section[class$="-summary"], .details section[class$="-details"], + .class-uses .detail, .serialized-class-details { + padding: 0 10px 5px 8px; + } + input#search-input { + width: 22vw; + } +} +@media screen and (max-width: 920px) { + .main-grid nav.toc { + display: none; + } + .top-nav nav.toc { + display: none; + position: absolute; + top: var(--top-nav-height); + left: 40vw; + width: 60vw; + z-index: 7; + background-color: var(--section-background-color); + box-sizing: border-box; + } + .top-nav nav.toc div.toc-header { + padding: 6px 15px; + font-size: 0.94em; + background-color: var(--section-background-color); + top: calc(var(--top-nav-height) + 10px); + } + .top-nav nav.toc ol.toc-list li { + font-size: 1.04em; + } + nav.toc a:link, nav.toc a:visited { + text-decoration:none; + color:var(--link-color); + } + nav.toc a[href]:hover, nav.toc a[href]:focus { + text-decoration:none; + color:var(--link-color-active); + } + :root { + scroll-behavior: auto; + } + header { + max-height: 100vh; + overflow-y: visible; + overscroll-behavior: contain; + } + nav { + overflow: visible; + } + ul.nav-list { + display: none; + position: absolute; + top: var(--top-nav-height); + overflow: auto; + z-index: 7; + background-color: var(--navbar-background-color); + width: 40%; + padding: 0; + box-sizing: border-box; + } + ul.nav-list li { + float: none; + padding: 6px; + margin-left: 10px; + margin-top: 2px; + } + .top-nav a:link, .top-nav a:active, .top-nav a:visited { + display: block; + } + .top-nav div.nav-menu-button { + flex: 1 1 auto; + } + .sub-nav ol.sub-nav-list { + margin-left: 4px; + padding-left: 4px; + } + button#navbar-toggle-button { + width: 3.4em; + height: 2.8em; + background-color: transparent; + display: block; + border: 0; + margin: 0 10px; + cursor: pointer; + font-size: 10px; + } + button#navbar-toggle-button .nav-bar-toggle-icon { + display: block; + width: 24px; + height: 3px; + margin: 4px 0; + border-radius: 2px; + background-color: var(--navbar-text-color); + } + button#navbar-toggle-button.expanded span.nav-bar-toggle-icon:nth-child(1) { + transform: rotate(45deg); + transform-origin: 10% 10%; + width: 26px; + } + button#navbar-toggle-button.expanded span.nav-bar-toggle-icon:nth-child(2) { + opacity: 0; + } + button#navbar-toggle-button.expanded span.nav-bar-toggle-icon:nth-child(3) { + transform: rotate(-45deg); + transform-origin: 10% 90%; + width: 26px; + } +} +@media screen and (max-width: 800px) { + .about-language { + padding-right: 16px; + max-width: 90%; + } + ul.nav-list li { + margin-left: 5px; + } + main { + padding: 10px 12px; + } + body { + -webkit-text-size-adjust: none; + } +} +@media screen and (max-width: 600px) { + .nav-list-search > a { + display: none; + } + input#search-input { + width: 18vw; + } + .summary section[class$="-summary"], .details section[class$="-details"], + .class-uses .detail, .serialized-class-details { + padding: 0; + } +} +pre.snippet { + background-color: var(--snippet-background-color); + color: var(--snippet-text-color); + padding: 10px; + margin: 12px 0; + overflow: auto; + white-space: pre; +} +div.snippet-container { + position: relative; +} +@media screen and (max-width: 800px) { + pre.snippet { + padding-top: 26px; + } + button.snippet-copy { + top: 4px; + right: 4px; + } +} +pre.snippet .italic { + font-style: italic; +} +pre.snippet .bold { + font-weight: bold; +} +pre.snippet .highlighted { + background-color: var(--snippet-highlight-color); + border-radius: 10%; +} diff --git a/docs/resource-files/x.png b/docs/resource-files/x.png new file mode 100644 index 0000000..30548a7 Binary files /dev/null and b/docs/resource-files/x.png differ diff --git a/docs/script-files/jquery-3.7.1.min.js b/docs/script-files/jquery-3.7.1.min.js new file mode 100644 index 0000000..7f37b5d --- /dev/null +++ b/docs/script-files/jquery-3.7.1.min.js @@ -0,0 +1,2 @@ +/*! jQuery v3.7.1 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(ie,e){"use strict";var oe=[],r=Object.getPrototypeOf,ae=oe.slice,g=oe.flat?function(e){return oe.flat.call(e)}:function(e){return oe.concat.apply([],e)},s=oe.push,se=oe.indexOf,n={},i=n.toString,ue=n.hasOwnProperty,o=ue.toString,a=o.call(Object),le={},v=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},y=function(e){return null!=e&&e===e.window},C=ie.document,u={type:!0,src:!0,nonce:!0,noModule:!0};function m(e,t,n){var r,i,o=(n=n||C).createElement("script");if(o.text=e,t)for(r in u)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[i.call(e)]||"object":typeof e}var t="3.7.1",l=/HTML$/i,ce=function(e,t){return new ce.fn.init(e,t)};function c(e){var t=!!e&&"length"in e&&e.length,n=x(e);return!v(e)&&!y(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+ge+")"+ge+"*"),x=new RegExp(ge+"|>"),j=new RegExp(g),A=new RegExp("^"+t+"$"),D={ID:new RegExp("^#("+t+")"),CLASS:new RegExp("^\\.("+t+")"),TAG:new RegExp("^("+t+"|[*])"),ATTR:new RegExp("^"+p),PSEUDO:new RegExp("^"+g),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+ge+"*(even|odd|(([+-]|)(\\d*)n|)"+ge+"*(?:([+-]|)"+ge+"*(\\d+)|))"+ge+"*\\)|)","i"),bool:new RegExp("^(?:"+f+")$","i"),needsContext:new RegExp("^"+ge+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+ge+"*((?:-\\d)?\\d*)"+ge+"*\\)|)(?=[^-]|$)","i")},N=/^(?:input|select|textarea|button)$/i,q=/^h\d$/i,L=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,H=/[+~]/,O=new RegExp("\\\\[\\da-fA-F]{1,6}"+ge+"?|\\\\([^\\r\\n\\f])","g"),P=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},M=function(){V()},R=J(function(e){return!0===e.disabled&&fe(e,"fieldset")},{dir:"parentNode",next:"legend"});try{k.apply(oe=ae.call(ye.childNodes),ye.childNodes),oe[ye.childNodes.length].nodeType}catch(e){k={apply:function(e,t){me.apply(e,ae.call(t))},call:function(e){me.apply(e,ae.call(arguments,1))}}}function I(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(V(e),e=e||T,C)){if(11!==p&&(u=L.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return k.call(n,a),n}else if(f&&(a=f.getElementById(i))&&I.contains(e,a)&&a.id===i)return k.call(n,a),n}else{if(u[2])return k.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&e.getElementsByClassName)return k.apply(n,e.getElementsByClassName(i)),n}if(!(h[t+" "]||d&&d.test(t))){if(c=t,f=e,1===p&&(x.test(t)||m.test(t))){(f=H.test(t)&&U(e.parentNode)||e)==e&&le.scope||((s=e.getAttribute("id"))?s=ce.escapeSelector(s):e.setAttribute("id",s=S)),o=(l=Y(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+Q(l[o]);c=l.join(",")}try{return k.apply(n,f.querySelectorAll(c)),n}catch(e){h(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return re(t.replace(ve,"$1"),e,n,r)}function W(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function F(e){return e[S]=!0,e}function $(e){var t=T.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function B(t){return function(e){return fe(e,"input")&&e.type===t}}function _(t){return function(e){return(fe(e,"input")||fe(e,"button"))&&e.type===t}}function z(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&R(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function X(a){return F(function(o){return o=+o,F(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function U(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}function V(e){var t,n=e?e.ownerDocument||e:ye;return n!=T&&9===n.nodeType&&n.documentElement&&(r=(T=n).documentElement,C=!ce.isXMLDoc(T),i=r.matches||r.webkitMatchesSelector||r.msMatchesSelector,r.msMatchesSelector&&ye!=T&&(t=T.defaultView)&&t.top!==t&&t.addEventListener("unload",M),le.getById=$(function(e){return r.appendChild(e).id=ce.expando,!T.getElementsByName||!T.getElementsByName(ce.expando).length}),le.disconnectedMatch=$(function(e){return i.call(e,"*")}),le.scope=$(function(){return T.querySelectorAll(":scope")}),le.cssHas=$(function(){try{return T.querySelector(":has(*,:jqfake)"),!1}catch(e){return!0}}),le.getById?(b.filter.ID=function(e){var t=e.replace(O,P);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(O,P);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):t.querySelectorAll(e)},b.find.CLASS=function(e,t){if("undefined"!=typeof t.getElementsByClassName&&C)return t.getElementsByClassName(e)},d=[],$(function(e){var t;r.appendChild(e).innerHTML="",e.querySelectorAll("[selected]").length||d.push("\\["+ge+"*(?:value|"+f+")"),e.querySelectorAll("[id~="+S+"-]").length||d.push("~="),e.querySelectorAll("a#"+S+"+*").length||d.push(".#.+[+~]"),e.querySelectorAll(":checked").length||d.push(":checked"),(t=T.createElement("input")).setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),r.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&d.push(":enabled",":disabled"),(t=T.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||d.push("\\["+ge+"*name"+ge+"*="+ge+"*(?:''|\"\")")}),le.cssHas||d.push(":has"),d=d.length&&new RegExp(d.join("|")),l=function(e,t){if(e===t)return a=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!le.sortDetached&&t.compareDocumentPosition(e)===n?e===T||e.ownerDocument==ye&&I.contains(ye,e)?-1:t===T||t.ownerDocument==ye&&I.contains(ye,t)?1:o?se.call(o,e)-se.call(o,t):0:4&n?-1:1)}),T}for(e in I.matches=function(e,t){return I(e,null,null,t)},I.matchesSelector=function(e,t){if(V(e),C&&!h[t+" "]&&(!d||!d.test(t)))try{var n=i.call(e,t);if(n||le.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){h(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(O,P),e[3]=(e[3]||e[4]||e[5]||"").replace(O,P),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||I.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&I.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return D.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&j.test(n)&&(t=Y(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(O,P).toLowerCase();return"*"===e?function(){return!0}:function(e){return fe(e,t)}},CLASS:function(e){var t=s[e+" "];return t||(t=new RegExp("(^|"+ge+")"+e+"("+ge+"|$)"))&&s(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=I.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function T(e,n,r){return v(n)?ce.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?ce.grep(e,function(e){return e===n!==r}):"string"!=typeof n?ce.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(ce.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||k,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:S.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof ce?t[0]:t,ce.merge(this,ce.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:C,!0)),w.test(r[1])&&ce.isPlainObject(t))for(r in t)v(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=C.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):v(e)?void 0!==n.ready?n.ready(e):e(ce):ce.makeArray(e,this)}).prototype=ce.fn,k=ce(C);var E=/^(?:parents|prev(?:Until|All))/,j={children:!0,contents:!0,next:!0,prev:!0};function A(e,t){while((e=e[t])&&1!==e.nodeType);return e}ce.fn.extend({has:function(e){var t=ce(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,Ce=/^$|^module$|\/(?:java|ecma)script/i;xe=C.createDocumentFragment().appendChild(C.createElement("div")),(be=C.createElement("input")).setAttribute("type","radio"),be.setAttribute("checked","checked"),be.setAttribute("name","t"),xe.appendChild(be),le.checkClone=xe.cloneNode(!0).cloneNode(!0).lastChild.checked,xe.innerHTML="",le.noCloneChecked=!!xe.cloneNode(!0).lastChild.defaultValue,xe.innerHTML="",le.option=!!xe.lastChild;var ke={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function Se(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&fe(e,t)?ce.merge([e],n):n}function Ee(e,t){for(var n=0,r=e.length;n",""]);var je=/<|&#?\w+;/;function Ae(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function Re(e,t){return fe(e,"table")&&fe(11!==t.nodeType?t:t.firstChild,"tr")&&ce(e).children("tbody")[0]||e}function Ie(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function We(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Fe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(_.hasData(e)&&(s=_.get(e).events))for(i in _.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),C.head.appendChild(r[0])},abort:function(){i&&i()}}});var Jt,Kt=[],Zt=/(=)\?(?=&|$)|\?\?/;ce.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Kt.pop()||ce.expando+"_"+jt.guid++;return this[e]=!0,e}}),ce.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Zt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Zt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=v(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Zt,"$1"+r):!1!==e.jsonp&&(e.url+=(At.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||ce.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=ie[r],ie[r]=function(){o=arguments},n.always(function(){void 0===i?ce(ie).removeProp(r):ie[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Kt.push(r)),o&&v(i)&&i(o[0]),o=i=void 0}),"script"}),le.createHTMLDocument=((Jt=C.implementation.createHTMLDocument("").body).innerHTML="
",2===Jt.childNodes.length),ce.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(le.createHTMLDocument?((r=(t=C.implementation.createHTMLDocument("")).createElement("base")).href=C.location.href,t.head.appendChild(r)):t=C),o=!n&&[],(i=w.exec(e))?[t.createElement(i[1])]:(i=Ae([e],t,o),o&&o.length&&ce(o).remove(),ce.merge([],i.childNodes)));var r,i,o},ce.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(ce.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},ce.expr.pseudos.animated=function(t){return ce.grep(ce.timers,function(e){return t===e.elem}).length},ce.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=ce.css(e,"position"),c=ce(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=ce.css(e,"top"),u=ce.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),v(t)&&(t=t.call(e,n,ce.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},ce.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){ce.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===ce.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===ce.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=ce(e).offset()).top+=ce.css(e,"borderTopWidth",!0),i.left+=ce.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-ce.css(r,"marginTop",!0),left:t.left-i.left-ce.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===ce.css(e,"position"))e=e.offsetParent;return e||J})}}),ce.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;ce.fn[t]=function(e){return M(this,function(e,t,n){var r;if(y(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),ce.each(["top","left"],function(e,n){ce.cssHooks[n]=Ye(le.pixelPosition,function(e,t){if(t)return t=Ge(e,n),_e.test(t)?ce(e).position()[n]+"px":t})}),ce.each({Height:"height",Width:"width"},function(a,s){ce.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){ce.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return M(this,function(e,t,n){var r;return y(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?ce.css(e,t,i):ce.style(e,t,n,i)},s,n?e:void 0,n)}})}),ce.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){ce.fn[t]=function(e){return this.on(t,e)}}),ce.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.on("mouseenter",e).on("mouseleave",t||e)}}),ce.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){ce.fn[n]=function(e,t){return 0",options:{classes:{},disabled:!1,create:null},_createWidget:function(t,e){e=x(e||this.defaultElement||this)[0],this.element=x(e),this.uuid=i++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=x(),this.hoverable=x(),this.focusable=x(),this.classesElementLookup={},e!==this&&(x.data(e,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===e&&this.destroy()}}),this.document=x(e.style?e.ownerDocument:e.document||e),this.window=x(this.document[0].defaultView||this.document[0].parentWindow)),this.options=x.widget.extend({},this.options,this._getCreateOptions(),t),this._create(),this.options.disabled&&this._setOptionDisabled(this.options.disabled),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:function(){return{}},_getCreateEventData:x.noop,_create:x.noop,_init:x.noop,destroy:function(){var i=this;this._destroy(),x.each(this.classesElementLookup,function(t,e){i._removeClass(e,t)}),this.element.off(this.eventNamespace).removeData(this.widgetFullName),this.widget().off(this.eventNamespace).removeAttr("aria-disabled"),this.bindings.off(this.eventNamespace)},_destroy:x.noop,widget:function(){return this.element},option:function(t,e){var i,s,n,o=t;if(0===arguments.length)return x.widget.extend({},this.options);if("string"==typeof t)if(o={},t=(i=t.split(".")).shift(),i.length){for(s=o[t]=x.widget.extend({},this.options[t]),n=0;n
"),i=e.children()[0];return x("body").append(e),t=i.offsetWidth,e.css("overflow","scroll"),t===(i=i.offsetWidth)&&(i=e[0].clientWidth),e.remove(),s=t-i},getScrollInfo:function(t){var e=t.isWindow||t.isDocument?"":t.element.css("overflow-x"),i=t.isWindow||t.isDocument?"":t.element.css("overflow-y"),e="scroll"===e||"auto"===e&&t.widthC(E(s),E(n))?o.important="horizontal":o.important="vertical",c.using.call(this,t,o)}),l.offset(x.extend(u,{using:t}))})},x.ui.position={fit:{left:function(t,e){var i=e.within,s=i.isWindow?i.scrollLeft:i.offset.left,n=i.width,o=t.left-e.collisionPosition.marginLeft,l=s-o,a=o+e.collisionWidth-n-s;e.collisionWidth>n?0n?0",delay:300,options:{icons:{submenu:"ui-icon-caret-1-e"},items:"> *",menus:"ul",position:{my:"left top",at:"right top"},role:"menu",blur:null,focus:null,select:null},_create:function(){this.activeMenu=this.element,this.mouseHandled=!1,this.lastMousePosition={x:null,y:null},this.element.uniqueId().attr({role:this.options.role,tabIndex:0}),this._addClass("ui-menu","ui-widget ui-widget-content"),this._on({"mousedown .ui-menu-item":function(t){t.preventDefault(),this._activateItem(t)},"click .ui-menu-item":function(t){var e=x(t.target),i=x(x.ui.safeActiveElement(this.document[0]));!this.mouseHandled&&e.not(".ui-state-disabled").length&&(this.select(t),t.isPropagationStopped()||(this.mouseHandled=!0),e.has(".ui-menu").length?this.expand(t):!this.element.is(":focus")&&i.closest(".ui-menu").length&&(this.element.trigger("focus",[!0]),this.active&&1===this.active.parents(".ui-menu").length&&clearTimeout(this.timer)))},"mouseenter .ui-menu-item":"_activateItem","mousemove .ui-menu-item":"_activateItem",mouseleave:"collapseAll","mouseleave .ui-menu":"collapseAll",focus:function(t,e){var i=this.active||this._menuItems().first();e||this.focus(t,i)},blur:function(t){this._delay(function(){x.contains(this.element[0],x.ui.safeActiveElement(this.document[0]))||this.collapseAll(t)})},keydown:"_keydown"}),this.refresh(),this._on(this.document,{click:function(t){this._closeOnDocumentClick(t)&&this.collapseAll(t,!0),this.mouseHandled=!1}})},_activateItem:function(t){var e,i;this.previousFilter||t.clientX===this.lastMousePosition.x&&t.clientY===this.lastMousePosition.y||(this.lastMousePosition={x:t.clientX,y:t.clientY},e=x(t.target).closest(".ui-menu-item"),i=x(t.currentTarget),e[0]===i[0]&&(i.is(".ui-state-active")||(this._removeClass(i.siblings().children(".ui-state-active"),null,"ui-state-active"),this.focus(t,i))))},_destroy:function(){var t=this.element.find(".ui-menu-item").removeAttr("role aria-disabled").children(".ui-menu-item-wrapper").removeUniqueId().removeAttr("tabIndex role aria-haspopup");this.element.removeAttr("aria-activedescendant").find(".ui-menu").addBack().removeAttr("role aria-labelledby aria-expanded aria-hidden aria-disabled tabIndex").removeUniqueId().show(),t.children().each(function(){var t=x(this);t.data("ui-menu-submenu-caret")&&t.remove()})},_keydown:function(t){var e,i,s,n=!0;switch(t.keyCode){case x.ui.keyCode.PAGE_UP:this.previousPage(t);break;case x.ui.keyCode.PAGE_DOWN:this.nextPage(t);break;case x.ui.keyCode.HOME:this._move("first","first",t);break;case x.ui.keyCode.END:this._move("last","last",t);break;case x.ui.keyCode.UP:this.previous(t);break;case x.ui.keyCode.DOWN:this.next(t);break;case x.ui.keyCode.LEFT:this.collapse(t);break;case x.ui.keyCode.RIGHT:this.active&&!this.active.is(".ui-state-disabled")&&this.expand(t);break;case x.ui.keyCode.ENTER:case x.ui.keyCode.SPACE:this._activate(t);break;case x.ui.keyCode.ESCAPE:this.collapse(t);break;default:e=this.previousFilter||"",s=n=!1,i=96<=t.keyCode&&t.keyCode<=105?(t.keyCode-96).toString():String.fromCharCode(t.keyCode),clearTimeout(this.filterTimer),i===e?s=!0:i=e+i,e=this._filterMenuItems(i),(e=s&&-1!==e.index(this.active.next())?this.active.nextAll(".ui-menu-item"):e).length||(i=String.fromCharCode(t.keyCode),e=this._filterMenuItems(i)),e.length?(this.focus(t,e),this.previousFilter=i,this.filterTimer=this._delay(function(){delete this.previousFilter},1e3)):delete this.previousFilter}n&&t.preventDefault()},_activate:function(t){this.active&&!this.active.is(".ui-state-disabled")&&(this.active.children("[aria-haspopup='true']").length?this.expand(t):this.select(t))},refresh:function(){var t,e,s=this,n=this.options.icons.submenu,i=this.element.find(this.options.menus);this._toggleClass("ui-menu-icons",null,!!this.element.find(".ui-icon").length),e=i.filter(":not(.ui-menu)").hide().attr({role:this.options.role,"aria-hidden":"true","aria-expanded":"false"}).each(function(){var t=x(this),e=t.prev(),i=x("").data("ui-menu-submenu-caret",!0);s._addClass(i,"ui-menu-icon","ui-icon "+n),e.attr("aria-haspopup","true").prepend(i),t.attr("aria-labelledby",e.attr("id"))}),this._addClass(e,"ui-menu","ui-widget ui-widget-content ui-front"),(t=i.add(this.element).find(this.options.items)).not(".ui-menu-item").each(function(){var t=x(this);s._isDivider(t)&&s._addClass(t,"ui-menu-divider","ui-widget-content")}),i=(e=t.not(".ui-menu-item, .ui-menu-divider")).children().not(".ui-menu").uniqueId().attr({tabIndex:-1,role:this._itemRole()}),this._addClass(e,"ui-menu-item")._addClass(i,"ui-menu-item-wrapper"),t.filter(".ui-state-disabled").attr("aria-disabled","true"),this.active&&!x.contains(this.element[0],this.active[0])&&this.blur()},_itemRole:function(){return{menu:"menuitem",listbox:"option"}[this.options.role]},_setOption:function(t,e){var i;"icons"===t&&(i=this.element.find(".ui-menu-icon"),this._removeClass(i,null,this.options.icons.submenu)._addClass(i,null,e.submenu)),this._super(t,e)},_setOptionDisabled:function(t){this._super(t),this.element.attr("aria-disabled",String(t)),this._toggleClass(null,"ui-state-disabled",!!t)},focus:function(t,e){var i;this.blur(t,t&&"focus"===t.type),this._scrollIntoView(e),this.active=e.first(),i=this.active.children(".ui-menu-item-wrapper"),this._addClass(i,null,"ui-state-active"),this.options.role&&this.element.attr("aria-activedescendant",i.attr("id")),i=this.active.parent().closest(".ui-menu-item").children(".ui-menu-item-wrapper"),this._addClass(i,null,"ui-state-active"),t&&"keydown"===t.type?this._close():this.timer=this._delay(function(){this._close()},this.delay),(i=e.children(".ui-menu")).length&&t&&/^mouse/.test(t.type)&&this._startOpening(i),this.activeMenu=e.parent(),this._trigger("focus",t,{item:e})},_scrollIntoView:function(t){var e,i,s;this._hasScroll()&&(i=parseFloat(x.css(this.activeMenu[0],"borderTopWidth"))||0,s=parseFloat(x.css(this.activeMenu[0],"paddingTop"))||0,e=t.offset().top-this.activeMenu.offset().top-i-s,i=this.activeMenu.scrollTop(),s=this.activeMenu.height(),t=t.outerHeight(),e<0?this.activeMenu.scrollTop(i+e):s",options:{appendTo:null,autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},requestIndex:0,pending:0,liveRegionTimer:null,_create:function(){var i,s,n,t=this.element[0].nodeName.toLowerCase(),e="textarea"===t,t="input"===t;this.isMultiLine=e||!t&&this._isContentEditable(this.element),this.valueMethod=this.element[e||t?"val":"text"],this.isNewMenu=!0,this._addClass("ui-autocomplete-input"),this.element.attr("autocomplete","off"),this._on(this.element,{keydown:function(t){if(this.element.prop("readOnly"))s=n=i=!0;else{s=n=i=!1;var e=x.ui.keyCode;switch(t.keyCode){case e.PAGE_UP:i=!0,this._move("previousPage",t);break;case e.PAGE_DOWN:i=!0,this._move("nextPage",t);break;case e.UP:i=!0,this._keyEvent("previous",t);break;case e.DOWN:i=!0,this._keyEvent("next",t);break;case e.ENTER:this.menu.active&&(i=!0,t.preventDefault(),this.menu.select(t));break;case e.TAB:this.menu.active&&this.menu.select(t);break;case e.ESCAPE:this.menu.element.is(":visible")&&(this.isMultiLine||this._value(this.term),this.close(t),t.preventDefault());break;default:s=!0,this._searchTimeout(t)}}},keypress:function(t){if(i)return i=!1,void(this.isMultiLine&&!this.menu.element.is(":visible")||t.preventDefault());if(!s){var e=x.ui.keyCode;switch(t.keyCode){case e.PAGE_UP:this._move("previousPage",t);break;case e.PAGE_DOWN:this._move("nextPage",t);break;case e.UP:this._keyEvent("previous",t);break;case e.DOWN:this._keyEvent("next",t)}}},input:function(t){if(n)return n=!1,void t.preventDefault();this._searchTimeout(t)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(t){clearTimeout(this.searching),this.close(t),this._change(t)}}),this._initSource(),this.menu=x("
    ").appendTo(this._appendTo()).menu({role:null}).hide().attr({unselectable:"on"}).menu("instance"),this._addClass(this.menu.element,"ui-autocomplete","ui-front"),this._on(this.menu.element,{mousedown:function(t){t.preventDefault()},menufocus:function(t,e){var i,s;if(this.isNewMenu&&(this.isNewMenu=!1,t.originalEvent&&/^mouse/.test(t.originalEvent.type)))return this.menu.blur(),void this.document.one("mousemove",function(){x(t.target).trigger(t.originalEvent)});s=e.item.data("ui-autocomplete-item"),!1!==this._trigger("focus",t,{item:s})&&t.originalEvent&&/^key/.test(t.originalEvent.type)&&this._value(s.value),(i=e.item.attr("aria-label")||s.value)&&String.prototype.trim.call(i).length&&(clearTimeout(this.liveRegionTimer),this.liveRegionTimer=this._delay(function(){this.liveRegion.html(x("
    ").text(i))},100))},menuselect:function(t,e){var i=e.item.data("ui-autocomplete-item"),s=this.previous;this.element[0]!==x.ui.safeActiveElement(this.document[0])&&(this.element.trigger("focus"),this.previous=s,this._delay(function(){this.previous=s,this.selectedItem=i})),!1!==this._trigger("select",t,{item:i})&&this._value(i.value),this.term=this._value(),this.close(t),this.selectedItem=i}}),this.liveRegion=x("
    ",{role:"status","aria-live":"assertive","aria-relevant":"additions"}).appendTo(this.document[0].body),this._addClass(this.liveRegion,null,"ui-helper-hidden-accessible"),this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_destroy:function(){clearTimeout(this.searching),this.element.removeAttr("autocomplete"),this.menu.element.remove(),this.liveRegion.remove()},_setOption:function(t,e){this._super(t,e),"source"===t&&this._initSource(),"appendTo"===t&&this.menu.element.appendTo(this._appendTo()),"disabled"===t&&e&&this.xhr&&this.xhr.abort()},_isEventTargetInWidget:function(t){var e=this.menu.element[0];return t.target===this.element[0]||t.target===e||x.contains(e,t.target)},_closeOnClickOutside:function(t){this._isEventTargetInWidget(t)||this.close()},_appendTo:function(){var t=this.options.appendTo;return t=!(t=!(t=t&&(t.jquery||t.nodeType?x(t):this.document.find(t).eq(0)))||!t[0]?this.element.closest(".ui-front, dialog"):t).length?this.document[0].body:t},_initSource:function(){var i,s,n=this;Array.isArray(this.options.source)?(i=this.options.source,this.source=function(t,e){e(x.ui.autocomplete.filter(i,t.term))}):"string"==typeof this.options.source?(s=this.options.source,this.source=function(t,e){n.xhr&&n.xhr.abort(),n.xhr=x.ajax({url:s,data:t,dataType:"json",success:function(t){e(t)},error:function(){e([])}})}):this.source=this.options.source},_searchTimeout:function(s){clearTimeout(this.searching),this.searching=this._delay(function(){var t=this.term===this._value(),e=this.menu.element.is(":visible"),i=s.altKey||s.ctrlKey||s.metaKey||s.shiftKey;t&&(e||i)||(this.selectedItem=null,this.search(null,s))},this.options.delay)},search:function(t,e){return t=null!=t?t:this._value(),this.term=this._value(),t.length").append(x("
    ").text(e.label)).appendTo(t)},_move:function(t,e){if(this.menu.element.is(":visible"))return this.menu.isFirstItem()&&/^previous/.test(t)||this.menu.isLastItem()&&/^next/.test(t)?(this.isMultiLine||this._value(this.term),void this.menu.blur()):void this.menu[t](e);this.search(null,e)},widget:function(){return this.menu.element},_value:function(){return this.valueMethod.apply(this.element,arguments)},_keyEvent:function(t,e){this.isMultiLine&&!this.menu.element.is(":visible")||(this._move(t,e),e.preventDefault())},_isContentEditable:function(t){if(!t.length)return!1;var e=t.prop("contentEditable");return"inherit"===e?this._isContentEditable(t.parent()):"true"===e}}),x.extend(x.ui.autocomplete,{escapeRegex:function(t){return t.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")},filter:function(t,e){var i=new RegExp(x.ui.autocomplete.escapeRegex(e),"i");return x.grep(t,function(t){return i.test(t.label||t.value||t)})}}),x.widget("ui.autocomplete",x.ui.autocomplete,{options:{messages:{noResults:"No search results.",results:function(t){return t+(1").text(e))},100))}});x.ui.autocomplete}); \ No newline at end of file diff --git a/docs/script-files/script.js b/docs/script-files/script.js new file mode 100644 index 0000000..b5b30fc --- /dev/null +++ b/docs/script-files/script.js @@ -0,0 +1,467 @@ +/* + * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ + */ + +var moduleSearchIndex; +var packageSearchIndex; +var typeSearchIndex; +var memberSearchIndex; +var tagSearchIndex; + +var oddRowColor = "odd-row-color"; +var evenRowColor = "even-row-color"; +var sortAsc = "sort-asc"; +var sortDesc = "sort-desc"; +var tableTab = "table-tab"; +var activeTableTab = "active-table-tab"; + +const linkIcon = "Link icon"; +const linkToSection = "Link to this section"; + +function loadScripts(doc, tag) { + createElem(doc, tag, 'script-files/search.js'); + + createElem(doc, tag, 'module-search-index.js'); + createElem(doc, tag, 'package-search-index.js'); + createElem(doc, tag, 'type-search-index.js'); + createElem(doc, tag, 'member-search-index.js'); + createElem(doc, tag, 'tag-search-index.js'); +} + +function createElem(doc, tag, path) { + var script = doc.createElement(tag); + var scriptElement = doc.getElementsByTagName(tag)[0]; + script.src = pathtoroot + path; + scriptElement.parentNode.insertBefore(script, scriptElement); +} + +// Helper for making content containing release names comparable lexicographically +function makeComparable(s) { + return s.toLowerCase().replace(/(\d+)/g, + function(n, m) { + return ("000" + m).slice(-4); + }); +} + +// Switches between two styles depending on a condition +function toggleStyle(classList, condition, trueStyle, falseStyle) { + if (condition) { + classList.remove(falseStyle); + classList.add(trueStyle); + } else { + classList.remove(trueStyle); + classList.add(falseStyle); + } +} + +// Sorts the rows in a table lexicographically by the content of a specific column +function sortTable(header, columnIndex, columns) { + var container = header.parentElement; + var descending = header.classList.contains(sortAsc); + container.querySelectorAll("div.table-header").forEach( + function(header) { + header.classList.remove(sortAsc); + header.classList.remove(sortDesc); + } + ) + var cells = container.children; + var rows = []; + for (var i = columns; i < cells.length; i += columns) { + rows.push(Array.prototype.slice.call(cells, i, i + columns)); + } + var comparator = function(a, b) { + var ka = makeComparable(a[columnIndex].textContent); + var kb = makeComparable(b[columnIndex].textContent); + if (ka < kb) + return descending ? 1 : -1; + if (ka > kb) + return descending ? -1 : 1; + return 0; + }; + var sorted = rows.sort(comparator); + var visible = 0; + sorted.forEach(function(row) { + if (row[0].style.display !== 'none') { + var isEvenRow = visible++ % 2 === 0; + } + row.forEach(function(cell) { + toggleStyle(cell.classList, isEvenRow, evenRowColor, oddRowColor); + container.appendChild(cell); + }) + }); + toggleStyle(header.classList, descending, sortDesc, sortAsc); +} + +// Toggles the visibility of a table category in all tables in a page +function toggleGlobal(checkbox, selected, columns) { + const display = checkbox.checked ? '' : 'none'; + const selectOther = selected === "other"; + const selectAll = selected === "all"; + if (selectAll) { + document.querySelectorAll('.checkboxes input[type="checkbox"]').forEach(c => { + c.checked = checkbox.checked; + }); + } + document.querySelectorAll("div.table-tabs").forEach(t => { + const id = t.parentElement.getAttribute("id"); + const selectedClass = id + "-tab" + (selectOther ? "" : selected); + var visible = 0; + t.parentElement.querySelectorAll('div.' + id) + .forEach(function(elem) { + if (selectAll + || (!selectOther && elem.classList.contains(selectedClass)) + || (selectOther && elem.className.indexOf(selectedClass) < 0)) { + elem.style.display = display; + } + if (elem.style.display === '') { + var isEvenRow = visible++ % (columns * 2) < columns; + toggleStyle(elem.classList, isEvenRow, evenRowColor, oddRowColor); + } + }); + var displaySection = visible === 0 ? 'none' : ''; + t.parentElement.style.display = displaySection; + document.querySelector("li#contents-" + id).style.display = displaySection; + }) +} + +// Shows the elements of a table belonging to a specific category +function show(tableId, selected, columns) { + if (tableId !== selected) { + document.querySelectorAll('div.' + tableId + ':not(.' + selected + ')') + .forEach(function(elem) { + elem.style.display = 'none'; + }); + } + document.querySelectorAll('div.' + selected) + .forEach(function(elem, index) { + elem.style.display = ''; + var isEvenRow = index % (columns * 2) < columns; + toggleStyle(elem.classList, isEvenRow, evenRowColor, oddRowColor); + }); + updateTabs(tableId, selected); +} + +function updateTabs(tableId, selected) { + document.getElementById(tableId + '.tabpanel') + .setAttribute('aria-labelledby', selected); + document.querySelectorAll('button[id^="' + tableId + '"]') + .forEach(function(tab, index) { + if (selected === tab.id || (tableId === selected && index === 0)) { + tab.className = activeTableTab; + tab.setAttribute('aria-selected', true); + tab.setAttribute('tabindex',0); + } else { + tab.className = tableTab; + tab.setAttribute('aria-selected', false); + tab.setAttribute('tabindex',-1); + } + }); +} + +function switchTab(e) { + var selected = document.querySelector('[aria-selected=true]'); + if (selected) { + if ((e.keyCode === 37 || e.keyCode === 38) && selected.previousSibling) { + // left or up arrow key pressed: move focus to previous tab + selected.previousSibling.click(); + selected.previousSibling.focus(); + e.preventDefault(); + } else if ((e.keyCode === 39 || e.keyCode === 40) && selected.nextSibling) { + // right or down arrow key pressed: move focus to next tab + selected.nextSibling.click(); + selected.nextSibling.focus(); + e.preventDefault(); + } + } +} + +var updateSearchResults = function() {}; + +function indexFilesLoaded() { + return moduleSearchIndex + && packageSearchIndex + && typeSearchIndex + && memberSearchIndex + && tagSearchIndex; +} +// Copy the contents of the local snippet to the clipboard +function copySnippet(button) { + copyToClipboard(button.nextElementSibling.innerText); + switchCopyLabel(button, button.firstElementChild); +} +function copyToClipboard(content) { + var textarea = document.createElement("textarea"); + textarea.style.height = 0; + document.body.appendChild(textarea); + textarea.value = content; + textarea.select(); + document.execCommand("copy"); + document.body.removeChild(textarea); +} +function switchCopyLabel(button, span) { + var copied = span.getAttribute("data-copied"); + button.classList.add("visible"); + var initialLabel = span.innerHTML; + span.innerHTML = copied; + setTimeout(function() { + button.classList.remove("visible"); + setTimeout(function() { + if (initialLabel !== copied) { + span.innerHTML = initialLabel; + } + }, 100); + }, 1900); +} +function setTopMargin() { + // Dynamically set scroll margin to accomodate for draft header + var headerHeight = Math.ceil(document.querySelector("header").offsetHeight); + document.querySelector(":root") + .style.setProperty("--nav-height", headerHeight + "px"); +} +document.addEventListener("readystatechange", (e) => { + if (document.readyState === "interactive") { + setTopMargin(); + } + if (sessionStorage.getItem("sidebar") === "hidden") { + const sidebar = document.querySelector(".main-grid nav.toc"); + if (sidebar) sidebar.classList.add("hide-sidebar"); + } +}); +document.addEventListener("DOMContentLoaded", function(e) { + setTopMargin(); + // Make sure current element is visible in breadcrumb navigation on small displays + const subnav = document.querySelector("ol.sub-nav-list"); + if (subnav && subnav.lastElementChild) { + subnav.lastElementChild.scrollIntoView({ behavior: "instant", inline: "start", block: "nearest" }); + } + // Clone TOC sidebar to header for mobile navigation + const navbar = document.querySelector("div#navbar-top"); + const sidebar = document.querySelector(".main-grid nav.toc"); + const main = document.querySelector(".main-grid main"); + const mainnav = navbar.querySelector("ul.nav-list"); + const toggleButton = document.querySelector("button#navbar-toggle-button"); + const toc = sidebar ? sidebar.cloneNode(true) : null; + if (toc) { + navbar.appendChild(toc); + } + document.querySelectorAll("input.filter-input").forEach(function(input) { + input.removeAttribute("disabled"); + input.setAttribute("autocapitalize", "off"); + input.value = ""; + input.addEventListener("input", function(e) { + const pattern = input.value ? input.value.trim() + .replace(/[\[\]{}()*+?.\\^$|]/g, '\\$&') + .replace(/\s+/g, ".*") : ""; + input.nextElementSibling.style.display = pattern ? "inline" : "none"; + const filter = new RegExp(pattern, "i"); + input.parentNode.parentNode.querySelectorAll("ol.toc-list li").forEach((li) => { + if (filter.test(li.innerText)) { + li.removeAttribute("style"); + } else { + li.style.display = "none"; + } + }); + if (expanded) { + expand(); + } + }); + }); + document.querySelectorAll("input.reset-filter").forEach((button) => { + button.removeAttribute("disabled"); + button.addEventListener("click", (e) => { + const input = button.previousElementSibling; + input.value = ""; + input.dispatchEvent(new InputEvent("input")); + input.focus(); + if (expanded) { + expand(); + } else { + prevHash = null; + handleScroll(); + } + }) + }); + var expanded = false; + var windowWidth; + function collapse() { + if (expanded) { + mainnav.removeAttribute("style"); + if (toc) { + toc.removeAttribute("style"); + } + toggleButton.classList.remove("expanded") + toggleButton.setAttribute("aria-expanded", "false"); + expanded = false; + } + } + function expand() { + expanded = true; + mainnav.style.display = "block"; + mainnav.style.removeProperty("height"); + var maxHeight = window.innerHeight - subnav.offsetTop + 4; + var expandedHeight = Math.min(maxHeight, mainnav.scrollHeight + 10); + if (toc) { + toc.style.display = "flex"; + expandedHeight = Math.min(maxHeight, + Math.max(expandedHeight, toc.querySelector("div.toc-header").offsetHeight + + toc.querySelector("ol.toc-list").scrollHeight + 10)); + toc.style.height = expandedHeight + "px"; + } + mainnav.style.height = expandedHeight + "px"; + toggleButton.classList.add("expanded"); + toggleButton.setAttribute("aria-expanded", "true"); + windowWidth = window.innerWidth; + } + toggleButton.addEventListener("click", (e) => { + if (expanded) { + collapse(); + } else { + expand(); + } + }); + if (toc) { + toc.querySelectorAll("a").forEach((link) => { + link.addEventListener("click", collapse); + }); + } + document.addEventListener('keydown', (e) => { + if (e.key === "Escape") collapse(); + }); + document.querySelector("main").addEventListener("click", collapse); + const searchInput = document.getElementById("search-input"); + if (searchInput) searchInput.addEventListener("focus", collapse); + document.querySelectorAll("h1, h2, h3, h4, h5, h6") + .forEach((hdr, idx) => { + // Create anchor links for headers with an associated id attribute + var id = hdr.getAttribute("id") || hdr.parentElement.getAttribute("id") + || (hdr.querySelector("a") && hdr.querySelector("a").getAttribute("id")); + if (id) { + var template = document.createElement('template'); + template.innerHTML =" " + linkIcon +""; + hdr.append(...template.content.childNodes); + } + }); + var sections; + var scrollTimeout; + var scrollTimeoutNeeded; + var prevHash; + function initSectionData() { + sections = [{ id: "", top: 0 }].concat(Array.from(main.querySelectorAll("section[id], h2[id], h2 a[id], div[id]")) + .filter((e) => { + return sidebar.querySelector("a[href=\"#" + encodeURI(e.getAttribute("id")) + "\"]") !== null + }).map((e) => { + return { + id: e.getAttribute("id"), + top: e.offsetTop + }; + })); + } + function setScrollTimeout() { + clearTimeout(scrollTimeout); + scrollTimeoutNeeded = false; + scrollTimeout = setTimeout(() => { + scrollTimeout = null; + handleScroll(); + }, 100); + } + function handleScroll() { + if (!sidebar || !sidebar.offsetParent || sidebar.classList.contains("hide-sidebar")) { + return; + } + if (scrollTimeout || scrollTimeoutNeeded) { + setScrollTimeout(); + return; + } + var scrollTop = document.documentElement.scrollTop; + var scrollHeight = document.documentElement.scrollHeight; + var currHash = null; + if (scrollHeight - scrollTop < window.innerHeight + 10) { + // Select last item if at bottom of the page + currHash = "#" + encodeURI(sections.at(-1).id); + } else { + for (var i = 0; i < sections.length; i++) { + var top = sections[i].top; + var bottom = sections[i + 1] ? sections[i + 1].top : scrollHeight; + if (top + ((bottom - top) / 2) > scrollTop || bottom > scrollTop + (window.innerHeight / 3)) { + currHash = "#" + encodeURI(sections[i].id); + break; + } + } + } + if (currHash !== prevHash) { + setSelected(currHash); + } + } + function setSelected(hash) { + var prev = sidebar.querySelector("a.current-selection"); + if (prev) + prev.classList.remove("current-selection"); + prevHash = hash; + if (hash) { + var curr = sidebar.querySelector("ol.toc-list a[href=\"" + hash + "\"]"); + if (curr) { + curr.classList.add("current-selection"); + curr.scrollIntoView({ behavior: "instant", block: "nearest" }); + } + } + } + if (sidebar) { + initSectionData(); + document.querySelectorAll("a[href^='#']").forEach((link) => { + link.addEventListener("click", (e) => { + scrollTimeoutNeeded = true; + setSelected(link.getAttribute("href")); + }) + }); + sidebar.querySelector("button.hide-sidebar").addEventListener("click", () => { + sidebar.classList.add("hide-sidebar"); + sessionStorage.setItem("sidebar", "hidden"); + }); + sidebar.querySelector("button.show-sidebar").addEventListener("click", () => { + sidebar.classList.remove("hide-sidebar"); + sessionStorage.removeItem("sidebar"); + initSectionData(); + handleScroll(); + }); + window.addEventListener("hashchange", (e) => { + scrollTimeoutNeeded = true; + }); + if (document.location.hash) { + scrollTimeoutNeeded = true; + setSelected(document.location.hash); + } else { + handleScroll(); + } + window.addEventListener("scroll", handleScroll); + window.addEventListener("scrollend", () => { + if (scrollTimeout) { + clearTimeout(scrollTimeout); + scrollTimeout = null; + handleScroll(); + } + }) + } + // Resize handler + function handleResize(e) { + if (expanded) { + if (windowWidth !== window.innerWidth) { + collapse(); + } else { + expand(); + } + } + if (sections) { + initSectionData(); + prevHash = null; + handleScroll(); + } + setTopMargin(); + } + window.addEventListener("orientationchange", handleResize); + window.addEventListener("resize", handleResize); +}); diff --git a/docs/script-files/search-page.js b/docs/script-files/search-page.js new file mode 100644 index 0000000..bcbd9b5 --- /dev/null +++ b/docs/script-files/search-page.js @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ + */ + +"use strict"; +$(function() { + var copy = $("#page-search-copy"); + var expand = $("#page-search-expand"); + var searchLink = $("span#page-search-link"); + var redirect = $("input#search-redirect"); + function setSearchUrlTemplate() { + var href = document.location.href.split(/[#?]/)[0]; + href += "?q=" + "%s"; + if (redirect.is(":checked")) { + href += "&r=1"; + } + searchLink.html(href); + copy[0].onmouseenter(); + } + function copyLink(e) { + copyToClipboard(this.previousSibling.innerText); + switchCopyLabel(this, this.lastElementChild); + } + copy.click(copyLink); + copy[0].onmouseenter = function() {}; + redirect.click(setSearchUrlTemplate); + setSearchUrlTemplate(); + copy.prop("disabled", false); + redirect.prop("disabled", false); + expand.click(function (e) { + var searchInfo = $("div.page-search-info"); + if(this.parentElement.hasAttribute("open")) { + searchInfo.attr("style", "border-width: 0;"); + } else { + searchInfo.attr("style", "border-width: 1px;").height(searchInfo.prop("scrollHeight")); + } + }); +}); +$(window).on("load", function() { + var input = $("#page-search-input"); + var reset = $("#page-search-reset"); + var notify = $("#page-search-notify"); + var resultSection = $("div#result-section"); + var resultContainer = $("div#result-container"); + var searchTerm = ""; + var activeTab = ""; + var fixedTab = false; + var visibleTabs = []; + var feelingLucky = false; + function renderResults(result) { + if (!result.length) { + notify.html(messages.noResult); + } else if (result.length === 1) { + notify.html(messages.oneResult); + } else { + notify.html(messages.manyResults.replace("{0}", result.length)); + } + resultContainer.empty(); + var r = { + "types": [], + "members": [], + "packages": [], + "modules": [], + "searchTags": [] + }; + for (var i in result) { + var item = result[i]; + var arr = r[item.category]; + arr.push(item); + } + if (!activeTab || r[activeTab].length === 0 || !fixedTab) { + Object.keys(r).reduce(function(prev, curr) { + if (r[curr].length > 0 && r[curr][0].score > prev) { + activeTab = curr; + return r[curr][0].score; + } + return prev; + }, 0); + } + if (feelingLucky && activeTab) { + notify.html(messages.redirecting) + var firstItem = r[activeTab][0]; + window.location = getURL(firstItem.indexItem, firstItem.category); + return; + } + if (result.length > 20) { + if (searchTerm[searchTerm.length - 1] === ".") { + if (activeTab === "types" && r["members"].length > r["types"].length) { + activeTab = "members"; + } else if (activeTab === "packages" && r["types"].length > r["packages"].length) { + activeTab = "types"; + } + } + } + var categoryCount = Object.keys(r).reduce(function(prev, curr) { + return prev + (r[curr].length > 0 ? 1 : 0); + }, 0); + visibleTabs = []; + var tabContainer = $("
    ").appendTo(resultContainer); + for (var key in r) { + var id = "#result-tab-" + key.replace("searchTags", "search_tags"); + if (r[key].length) { + var count = r[key].length >= 1000 ? "999+" : r[key].length; + if (result.length > 20 && categoryCount > 1) { + var button = $("").appendTo(tabContainer); + button.click(key, function(e) { + fixedTab = true; + renderResult(e.data, $(this)); + }); + visibleTabs.push(key); + } else { + $("" + categories[key] + + " (" + count + ")").appendTo(tabContainer); + renderTable(key, r[key]).appendTo(resultContainer); + tabContainer = $("
    ").appendTo(resultContainer); + + } + } + } + if (activeTab && result.length > 20 && categoryCount > 1) { + $("button#result-tab-" + activeTab).addClass("active-table-tab"); + renderTable(activeTab, r[activeTab]).appendTo(resultContainer); + } + resultSection.show(); + function renderResult(category, button) { + activeTab = category; + setSearchUrl(); + resultContainer.find("div.summary-table").remove(); + renderTable(activeTab, r[activeTab]).appendTo(resultContainer); + button.siblings().removeClass("active-table-tab"); + button.addClass("active-table-tab"); + } + } + function selectTab(category) { + $("button#result-tab-" + category).click(); + } + function renderTable(category, items) { + var table = $("
    ") + .addClass(category === "modules" + ? "one-column-search-results" + : "two-column-search-results"); + var col1, col2; + if (category === "modules") { + col1 = "Module"; + } else if (category === "packages") { + col1 = "Module"; + col2 = "Package"; + } else if (category === "types") { + col1 = "Package"; + col2 = "Class" + } else if (category === "members") { + col1 = "Class"; + col2 = "Member"; + } else if (category === "searchTags") { + col1 = "Location"; + col2 = "Name"; + } + $("
    " + col1 + "
    ").appendTo(table); + if (category !== "modules") { + $("
    " + col2 + "
    ").appendTo(table); + } + $.each(items, function(index, item) { + var rowColor = index % 2 ? "odd-row-color" : "even-row-color"; + renderItem(item, table, rowColor); + }); + return table; + } + function renderItem(item, table, rowColor) { + var label = getHighlightedText(item.input, item.boundaries, item.prefix.length, item.input.length); + var link = $("") + .attr("href", getURL(item.indexItem, item.category)) + .attr("tabindex", "0") + .addClass("search-result-link") + .html(label); + var container = getHighlightedText(item.input, item.boundaries, 0, item.prefix.length - 1); + if (item.category === "searchTags") { + container = item.indexItem.h || ""; + } + if (item.category !== "modules") { + $("
    ").html(container).addClass("col-plain").addClass(rowColor).appendTo(table); + } + $("
    ").html(link).addClass("col-last").addClass(rowColor).appendTo(table); + } + var timeout; + function schedulePageSearch() { + if (timeout) { + clearTimeout(timeout); + } + timeout = setTimeout(function () { + doPageSearch() + }, 100); + } + function doPageSearch() { + setSearchUrl(); + var term = searchTerm = input.val().trim(); + if (term === "") { + notify.html(messages.enterTerm); + activeTab = ""; + fixedTab = false; + resultContainer.empty(); + resultSection.hide(); + } else { + notify.html(messages.searching); + doSearch({ term: term, maxResults: 1200 }, renderResults); + } + } + function setSearchUrl() { + var query = input.val().trim(); + var url = document.location.pathname; + if (query) { + url += "?q=" + encodeURI(query); + if (activeTab && fixedTab) { + url += "&c=" + activeTab; + } + } + history.replaceState({query: query}, "", url); + } + input.on("input", function(e) { + feelingLucky = false; + schedulePageSearch(); + }); + $(document).keydown(function(e) { + if ((e.ctrlKey || e.metaKey) && (e.key === "ArrowLeft" || e.key === "ArrowRight")) { + if (activeTab && visibleTabs.length > 1) { + var idx = visibleTabs.indexOf(activeTab); + idx += e.key === "ArrowLeft" ? visibleTabs.length - 1 : 1; + selectTab(visibleTabs[idx % visibleTabs.length]); + return false; + } + } + }); + reset.click(function() { + notify.html(messages.enterTerm); + resultSection.hide(); + activeTab = ""; + fixedTab = false; + resultContainer.empty(); + input.val('').focus(); + setSearchUrl(); + }); + input.prop("disabled", false); + input.attr("autocapitalize", "off"); + reset.prop("disabled", false); + + var urlParams = new URLSearchParams(window.location.search); + if (urlParams.has("q")) { + input.val(urlParams.get("q")) + } + if (urlParams.has("c")) { + activeTab = urlParams.get("c"); + fixedTab = true; + } + if (urlParams.get("r")) { + feelingLucky = true; + } + if (input.val()) { + doPageSearch(); + } else { + notify.html(messages.enterTerm); + } + input.select().focus(); +}); diff --git a/docs/script-files/search.js b/docs/script-files/search.js new file mode 100644 index 0000000..0486cc6 --- /dev/null +++ b/docs/script-files/search.js @@ -0,0 +1,436 @@ +/* + * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ + */ +"use strict"; +const messages = { + enterTerm: "Enter a search term", + noResult: "No results found", + oneResult: "Found one result", + manyResults: "Found {0} results", + loading: "Loading search index...", + searching: "Searching...", + redirecting: "Redirecting to first result...", +} +const categories = { + modules: "Modules", + packages: "Packages", + types: "Classes and Interfaces", + members: "Members", + searchTags: "Search Tags" +}; +const highlight = "$&"; +const NO_MATCH = {}; +const MAX_RESULTS = 300; +const UNICODE_LETTER = 0; +const UNICODE_DIGIT = 1; +const UNICODE_OTHER = 2; +function checkUnnamed(name, separator) { + return name === "" || !name ? "" : name + separator; +} +function escapeHtml(str) { + return str.replace(//g, ">"); +} +function getHighlightedText(str, boundaries, from, to) { + var start = from; + var text = ""; + for (var i = 0; i < boundaries.length; i += 2) { + var b0 = boundaries[i]; + var b1 = boundaries[i + 1]; + if (b0 >= to || b1 <= from) { + continue; + } + text += escapeHtml(str.slice(start, Math.max(start, b0))); + text += ""; + text += escapeHtml(str.slice(Math.max(start, b0), Math.min(to, b1))); + text += ""; + start = Math.min(to, b1); + } + text += escapeHtml(str.slice(start, to)); + return text; +} +function getURLPrefix(item, category) { + var urlPrefix = ""; + var slash = "/"; + if (category === "modules") { + return item.l + slash; + } else if (category === "packages" && item.m) { + return item.m + slash; + } else if (category === "types" || category === "members") { + if (item.m) { + urlPrefix = item.m + slash; + } else { + $.each(packageSearchIndex, function(index, it) { + if (it.m && item.p === it.l) { + urlPrefix = it.m + slash; + } + }); + } + } + return urlPrefix; +} +function getURL(item, category) { + if (item.url) { + return item.url; + } + var url = getURLPrefix(item, category); + if (category === "modules") { + url += "module-summary.html"; + } else if (category === "packages") { + if (item.u) { + url = item.u; + } else { + url += item.l.replace(/\./g, '/') + "/package-summary.html"; + } + } else if (category === "types") { + if (item.u) { + url = item.u; + } else { + url += checkUnnamed(item.p, "/").replace(/\./g, '/') + item.l + ".html"; + } + } else if (category === "members") { + url += checkUnnamed(item.p, "/").replace(/\./g, '/') + item.c + ".html" + "#"; + if (item.u) { + url += item.u; + } else { + url += item.l; + } + } else if (category === "searchTags") { + url += item.u; + } + item.url = url; + return url; +} +function createMatcher(term, camelCase) { + if (camelCase && !isUpperCase(term)) { + return null; // no need for camel-case matcher for lower case query + } + var pattern = ""; + var upperCase = []; + term.trim().split(/\s+/).forEach(function(w, index, array) { + var tokens = w.split(/(?=[\p{Lu},.()<>?[\/])/u); + for (var i = 0; i < tokens.length; i++) { + var s = tokens[i]; + // ',' and '?' are the only delimiters commonly followed by space in java signatures + pattern += "(" + escapeUnicodeRegex(s).replace(/[,?]/g, "$&\\s*?") + ")"; + upperCase.push(false); + var isWordToken = /[\p{L}\p{Nd}_]$/u.test(s); + if (isWordToken) { + if (i === tokens.length - 1 && index < array.length - 1) { + // space in query string matches all delimiters + pattern += "(.*?)"; + upperCase.push(isUpperCase(s[0])); + } else { + if (!camelCase && isUpperCase(s) && s.length === 1) { + pattern += "()"; + } else { + pattern += "([\\p{L}\\p{Nd}\\p{Sc}<>?[\\]]*?)"; + } + upperCase.push(isUpperCase(s[0])); + } + } else { + pattern += "()"; + upperCase.push(false); + } + } + }); + var re = new RegExp(pattern, "gui"); + re.upperCase = upperCase; + return re; +} +// Unicode regular expressions do not allow certain characters to be escaped +function escapeUnicodeRegex(pattern) { + return pattern.replace(/[\[\]{}()*+?.\\^$|\s]/g, '\\$&'); +} +function findMatch(matcher, input, startOfName, endOfName) { + var from = startOfName; + matcher.lastIndex = from; + var match = matcher.exec(input); + // Expand search area until we get a valid result or reach the beginning of the string + while (!match || match.index + match[0].length < startOfName || endOfName < match.index) { + if (from === 0) { + return NO_MATCH; + } + from = input.lastIndexOf(".", from - 2) + 1; + matcher.lastIndex = from; + match = matcher.exec(input); + } + var boundaries = []; + var matchEnd = match.index + match[0].length; + var score = 5; + var start = match.index; + var prevEnd = -1; + for (var i = 1; i < match.length; i += 2) { + var charType = getCharType(input[start]); + var isMatcherUpper = matcher.upperCase[i]; + // capturing groups come in pairs, match and non-match + boundaries.push(start, start + match[i].length); + // make sure groups are anchored on a left word boundary + var prevChar = input[start - 1] || ""; + var nextChar = input[start + 1] || ""; + if (start !== 0) { + if (charType === UNICODE_DIGIT && getCharType(prevChar) === UNICODE_DIGIT) { + return NO_MATCH; + } else if (charType === UNICODE_LETTER && getCharType(prevChar) === UNICODE_LETTER) { + var isUpper = isUpperCase(input[start]); + if (isUpper && (isLowerCase(prevChar) || isLowerCase(nextChar))) { + score -= 0.1; + } else if (isMatcherUpper && start === prevEnd) { + score -= isUpper ? 0.1 : 1.0; + } else { + return NO_MATCH; + } + } + } + prevEnd = start + match[i].length; + start += match[i].length + match[i + 1].length; + + // lower score for parts of the name that are missing + if (match[i + 1] && prevEnd < endOfName) { + score -= rateNoise(match[i + 1]); + } + } + // lower score if a type name contains unmatched camel-case parts + if (input[matchEnd - 1] !== "." && endOfName > matchEnd) + score -= rateNoise(input.slice(matchEnd, endOfName)); + score -= rateNoise(input.slice(0, Math.max(startOfName, match.index))); + + if (score <= 0) { + return NO_MATCH; + } + return { + input: input, + score: score, + boundaries: boundaries + }; +} +function isLetter(s) { + return /\p{L}/u.test(s); +} +function isUpperCase(s) { + return /\p{Lu}/u.test(s); +} +function isLowerCase(s) { + return /\p{Ll}/u.test(s); +} +function isDigit(s) { + return /\p{Nd}/u.test(s); +} +function getCharType(s) { + if (isLetter(s)) { + return UNICODE_LETTER; + } else if (isDigit(s)) { + return UNICODE_DIGIT; + } else { + return UNICODE_OTHER; + } +} +function rateNoise(str) { + return (str.match(/([.(])/g) || []).length / 5 + + (str.match(/(\p{Lu}+)/gu) || []).length / 10 + + str.length / 20; +} +function doSearch(request, response) { + var term = request.term.trim(); + var maxResults = request.maxResults || MAX_RESULTS; + var matcher = { + plainMatcher: createMatcher(term, false), + camelCaseMatcher: createMatcher(term, true) + } + var indexLoaded = indexFilesLoaded(); + + function getPrefix(item, category) { + switch (category) { + case "packages": + return checkUnnamed(item.m, "/"); + case "types": + return checkUnnamed(item.p, "."); + case "members": + return checkUnnamed(item.p, ".") + item.c + "."; + default: + return ""; + } + } + function useQualifiedName(category) { + switch (category) { + case "packages": + return /[\s/]/.test(term); + case "types": + case "members": + return /[\s.]/.test(term); + default: + return false; + } + } + function searchIndex(indexArray, category) { + var matches = []; + if (!indexArray) { + if (!indexLoaded) { + matches.push({ l: messages.loading, category: category }); + } + return matches; + } + $.each(indexArray, function (i, item) { + var prefix = getPrefix(item, category); + var simpleName = item.l; + var qualifiedName = prefix + simpleName; + var useQualified = useQualifiedName(category); + var input = useQualified ? qualifiedName : simpleName; + var startOfName = useQualified ? prefix.length : 0; + var endOfName = category === "members" && input.indexOf("(", startOfName) > -1 + ? input.indexOf("(", startOfName) : input.length; + var m = findMatch(matcher.plainMatcher, input, startOfName, endOfName); + if (m === NO_MATCH && matcher.camelCaseMatcher) { + m = findMatch(matcher.camelCaseMatcher, input, startOfName, endOfName); + } + if (m !== NO_MATCH) { + m.indexItem = item; + m.prefix = prefix; + m.category = category; + if (!useQualified) { + m.input = qualifiedName; + m.boundaries = m.boundaries.map(function(b) { + return b + prefix.length; + }); + } + matches.push(m); + } + return true; + }); + return matches.sort(function(e1, e2) { + return e2.score - e1.score; + }).slice(0, maxResults); + } + + var result = searchIndex(moduleSearchIndex, "modules") + .concat(searchIndex(packageSearchIndex, "packages")) + .concat(searchIndex(typeSearchIndex, "types")) + .concat(searchIndex(memberSearchIndex, "members")) + .concat(searchIndex(tagSearchIndex, "searchTags")); + + if (!indexLoaded) { + updateSearchResults = function() { + doSearch(request, response); + } + } else { + updateSearchResults = function() {}; + } + response(result); +} +// JQuery search menu implementation +$.widget("custom.catcomplete", $.ui.autocomplete, { + _create: function() { + this._super(); + this.widget().menu("option", "items", "> .result-item"); + // workaround for search result scrolling + this.menu._scrollIntoView = function _scrollIntoView( item ) { + var borderTop, paddingTop, offset, scroll, elementHeight, itemHeight; + if ( this._hasScroll() ) { + borderTop = parseFloat( $.css( this.activeMenu[ 0 ], "borderTopWidth" ) ) || 0; + paddingTop = parseFloat( $.css( this.activeMenu[ 0 ], "paddingTop" ) ) || 0; + offset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop; + scroll = this.activeMenu.scrollTop(); + elementHeight = this.activeMenu.height() - 26; + itemHeight = item.outerHeight(); + + if ( offset < 0 ) { + this.activeMenu.scrollTop( scroll + offset ); + } else if ( offset + itemHeight > elementHeight ) { + this.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight ); + } + } + }; + }, + _renderMenu: function(ul, items) { + var currentCategory = ""; + var widget = this; + widget.menu.bindings = $(); + $.each(items, function(index, item) { + if (item.category && item.category !== currentCategory) { + ul.append("
  • " + categories[item.category] + "
  • "); + currentCategory = item.category; + } + var li = widget._renderItemData(ul, item); + if (item.category) { + li.attr("aria-label", categories[item.category] + " : " + item.l); + } else { + li.attr("aria-label", item.l); + } + li.attr("class", "result-item"); + }); + ul.append("
    "); + }, + _renderItem: function(ul, item) { + var li = $("
  • ").appendTo(ul); + var div = $("
    ").appendTo(li); + var label = item.l + ? item.l + : getHighlightedText(item.input, item.boundaries, 0, item.input.length); + var idx = item.indexItem; + if (item.category === "searchTags" && idx && idx.h) { + if (idx.d) { + div.html(label + " (" + idx.h + ")
    " + + idx.d + "
    "); + } else { + div.html(label + " (" + idx.h + ")"); + } + } else { + div.html(label); + } + return li; + } +}); +$(function() { + var search = $("#search-input"); + var reset = $("#reset-search"); + search.catcomplete({ + minLength: 1, + delay: 200, + source: function(request, response) { + reset.css("display", "inline"); + if (request.term.trim() === "") { + return this.close(); + } + return doSearch(request, response); + }, + response: function(event, ui) { + if (!ui.content.length) { + ui.content.push({ l: messages.noResult }); + } else { + $("#search-input").empty(); + } + }, + close: function(event, ui) { + reset.css("display", search.val() ? "inline" : "none"); + }, + change: function(event, ui) { + reset.css("display", search.val() ? "inline" : "none"); + }, + autoFocus: true, + focus: function(event, ui) { + return false; + }, + position: { + collision: "flip" + }, + select: function(event, ui) { + if (ui.item.indexItem) { + var url = getURL(ui.item.indexItem, ui.item.category); + window.location.href = pathtoroot + url; + $("#search-input").focus(); + } + } + }); + search.val(''); + search.prop("disabled", false); + search.attr("autocapitalize", "off"); + reset.prop("disabled", false); + reset.click(function() { + search.val('').focus(); + reset.css("display", "none"); + }); + search.focus(); +}); diff --git a/docs/search.html b/docs/search.html new file mode 100644 index 0000000..a9c0f9f --- /dev/null +++ b/docs/search.html @@ -0,0 +1,79 @@ + + + + +Search (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
    + +
    +
    +
    +

    Search

    +
    + + +
    +Additional resources +
    +
    +
    +

    The help page provides an introduction to the scope and syntax of JavaDoc search.

    +

    You can use the <ctrl> or <cmd> keys in combination with the left and right arrow keys to switch between result tabs in this page.

    +

    The URL template below may be used to configure this page as a search engine in browsers that support this feature. It has been tested to work in Google Chrome and Mozilla Firefox. Note that other browsers may not support this feature or require a different URL format.

    +link +

    + +

    +
    +

    Loading search index...

    + +
    +
    + +
    +
    +
    + + diff --git a/docs/serialized-form.html b/docs/serialized-form.html new file mode 100644 index 0000000..e321319 --- /dev/null +++ b/docs/serialized-form.html @@ -0,0 +1,83 @@ + + + + +Serialized Form (CustomRecipes 2.0.5.4 API) + + + + + + + + + + + + + + +
    + +
    +
    +
    +
    +

    Serialized Form

    +
    + +
    +
    + +
    +
    +
    + + diff --git a/docs/tag-search-index.js b/docs/tag-search-index.js new file mode 100644 index 0000000..d89a0cd --- /dev/null +++ b/docs/tag-search-index.js @@ -0,0 +1 @@ +tagSearchIndex = [{"l":"Key features:","h":"class me.mehboss.utils.RecipeUtil.Recipe","d":"Section","u":"me/mehboss/utils/RecipeUtil.Recipe.html#key-features--heading"},{"l":"Recipe Registration","h":"class me.mehboss.utils.RecipeUtil","d":"Section","u":"me/mehboss/utils/RecipeUtil.html#recipe-registration-heading"},{"l":"Serialized Form","h":"","u":"serialized-form.html"},{"l":"Validation rules include:","h":"class me.mehboss.utils.RecipeUtil","d":"Section","u":"me/mehboss/utils/RecipeUtil.html#validation-rules-include--heading"}];updateSearchResults(); \ No newline at end of file diff --git a/docs/type-search-index.js b/docs/type-search-index.js new file mode 100644 index 0000000..bc481f7 --- /dev/null +++ b/docs/type-search-index.js @@ -0,0 +1 @@ +typeSearchIndex = [{"l":"All Classes and Interfaces","u":"allclasses-index.html"},{"p":"me.mehboss.utils","l":"RecipeConditions.Block"},{"p":"me.mehboss.brewing","l":"BrewAction"},{"p":"me.mehboss.brewing","l":"Brewery"},{"p":"me.mehboss.brewing","l":"BrewEvent"},{"p":"me.mehboss.brewing","l":"BrewingRecipe"},{"p":"me.mehboss.utils.data","l":"BrewingRecipeData"},{"p":"me.mehboss.commands","l":"CommandBook"},{"p":"me.mehboss.commands","l":"CommandCrafterDebug"},{"p":"me.mehboss.commands","l":"CommandDebug"},{"p":"me.mehboss.commands","l":"CommandEditItem"},{"p":"me.mehboss.commands","l":"CommandGive"},{"p":"me.mehboss.commands","l":"CommandGUI"},{"p":"me.mehboss.commands","l":"CommandHelp"},{"p":"me.mehboss.commands","l":"CommandItems"},{"p":"me.mehboss.commands","l":"CommandList"},{"p":"me.mehboss.recipe","l":"CommandListener"},{"p":"me.mehboss.commands","l":"CommandRecipe"},{"p":"me.mehboss.commands","l":"CommandReload"},{"p":"me.mehboss.commands","l":"CommandRemove"},{"p":"me.mehboss.commands","l":"CommandShow"},{"p":"me.mehboss.utils","l":"CompatibilityUtil"},{"p":"me.mehboss.utils","l":"RecipeConditions.ConditionSet"},{"p":"me.mehboss.utils.data","l":"CookingRecipeData"},{"p":"me.mehboss.utils","l":"CooldownManager.Cooldown"},{"p":"me.mehboss.utils","l":"CooldownManager"},{"p":"me.mehboss.utils.data","l":"CraftingRecipeData"},{"p":"me.mehboss.commands","l":"CRCommand"},{"p":"me.mehboss.utils","l":"RecipeUtil.Ingredient"},{"p":"me.mehboss.utils","l":"InvalidRecipeException"},{"p":"me.mehboss.utils","l":"InventoryUtils"},{"p":"me.mehboss.recipe","l":"Main"},{"p":"me.mehboss.utils","l":"MetaChecks"},{"p":"me.mehboss.utils","l":"RecipeUtil.Recipe"},{"p":"me.mehboss.utils","l":"RecipeConditions"},{"p":"me.mehboss.utils","l":"RecipeUtil.Recipe.RecipeType"},{"p":"me.mehboss.utils","l":"RecipeUtil"},{"p":"me.mehboss.commands","l":"TabCompletion"},{"p":"me.mehboss.utils","l":"RecipeConditions.Block.TimeRange"},{"p":"me.mehboss.utils.data","l":"WorkstationRecipeData"}];updateSearchResults(); \ No newline at end of file diff --git a/pom.xml b/pom.xml index 6555305..4b28af7 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 CustomRecipes CustomRecipes - 2.0.5.1 + 2.0.5.6 src @@ -15,6 +15,26 @@ + + org.apache.maven.plugins + maven-javadoc-plugin + 3.5.0 + + false + true + + me.mehboss.recipe.* + + + + + + javadoc + + + + maven-compiler-plugin 3.8.1 @@ -27,32 +47,6 @@ org.apache.maven.plugins maven-shade-plugin 3.6.0 - - - - - - - - *:* - - com/cryptomorin/xseries/XBiome* - com/cryptomorin/xseries/XBlock* - com/cryptomorin/xseries/XEntity* - com/cryptomorin/xseries/XEntityType* - com/cryptomorin/xseries/XItemStack* - com/cryptomorin/xseries/XWorldBorder* - com/cryptomorin/xseries/XTag* - com/cryptomorin/xseries/NMSExtras* - com/cryptomorin/xseries/NoteBlockMusic* - - com/cryptomorin/xseries/SkullCacheListener* - - - - package @@ -61,6 +55,26 @@ + + true + + + *:* + + + + + de.tr7zw.changeme.nbtapi + me.mehboss.utils.nbtapi + + + com.cryptomorin.xseries + me.mehboss.shaded.xseries + + + ${project.artifactId}-${project.version} + false + @@ -80,8 +94,7 @@ EcoEnchants - - https://repo.auxilor.io/repository/maven-public/ + https://repo.auxilor.io/repository/maven-public/ jitpack-repo @@ -126,7 +139,7 @@ com.github.cryptomorin XSeries - 13.3.3 + 13.5.1 com.willfp @@ -136,7 +149,7 @@ io.github.bananapuncher714 nbteditor - 7.19.10 + 7.20.1 com.github.LoneDev6 @@ -163,6 +176,11 @@ 6.9.5-SNAPSHOT provided + + de.tr7zw + item-nbt-api + 2.15.3 + io.th0rgal oraxen @@ -238,5 +256,11 @@ minimal-json 0.9.5 + + com.github.Valorless + HavenBags + 156279014a + provided + \ No newline at end of file diff --git a/resources/config.yml b/resources/config.yml index 91a4b24..e36d2d6 100644 --- a/resources/config.yml +++ b/resources/config.yml @@ -1,6 +1,6 @@ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # CustomRecipe remastered by MehBoss # -# Version v2.0.5.1 # +# Version v2.0.5.6 # # # # If you find a bug, send me a PM and it will be fixed! :) # # # @@ -25,6 +25,7 @@ firstLoad: true # internally managed do not remove Debug: false Crafter-Debug: false Update-Check: true +Recipes-Alias: true Messages: Invalid-Args: '&cInvalid args! (use: /crecipe give [amount])' Invalid-Perms: '&cYou are not allowed to use this command!' diff --git a/resources/plugin.yml b/resources/plugin.yml index b86af59..65ccdfe 100644 --- a/resources/plugin.yml +++ b/resources/plugin.yml @@ -9,12 +9,11 @@ commands: crecipe: description: Displays the custom recipe help page. aliases: [crecipes, customrecipe, customrecipes, customr, cr] - permissions: - crecipe.*: - default: op edititem: description: NBT Editor for items! aliases: [ei, edit] - permissions: - crecipe.*: - default: op \ No newline at end of file + permission: crecipe.edititem + recipes: + description: Opens the recipe booklet! + aliases: [recipe] + permission: crecipe.book \ No newline at end of file diff --git a/resources/recipes/CursedPick.yml b/resources/recipes/CursedPick.yml index f0aafae..9436160 100644 --- a/resources/recipes/CursedPick.yml +++ b/resources/recipes/CursedPick.yml @@ -7,22 +7,17 @@ CursedPick: # Set to true for shapeless Shapeless: false + # Defines a group, valid only for crafting recipes + # Empty string denotes no group + # Ommit or set to none if not needed + Group: '' + # Defines a cooldown for crafting this item. # Define amount of time in seconds before the player can craft # this recipe again # Set to -1 or Omit if not needed Cooldown: 60 - # Item ENUM for the crafted item - Item: 'DIAMOND_PICKAXE' - - # Specific item type (e.g. "19/00" or "13:4"). - # Ommit or set to none if not needed - Item-Damage: 'none' - - # The quantity received upon crafting - Amount: 1 - # Can this item be placed as a block? Placeable: true @@ -35,10 +30,6 @@ CursedPick: # Improves performance Custom-Tagged: true - # 1-100 durability. - # Omit or set to 100 if not applicable - Durability: '100' - # Unique identifier for the recipe # Also known as the NamespacedKey Identifier: 'CURSED_PICKAXE' @@ -60,27 +51,48 @@ CursedPick: # Category recipe is to be shown in the recipe book Book-Category: 'MISC' - # Display name with color codes. - # Omit or set to none if not needed - Name: '&4&lCursed Pick' - - # Lore for the item, can be multiple lines. - # Use [] if not needed - Lore: - - '&c&lThe Cursed Pickaxe' + Result: + # Item ENUM for the crafted item + Item: 'DIAMOND_PICKAXE' + # Specific item type (e.g. "19/00" or "13:4"). + # Ommit or set to none if not needed + Item-Damage: 'none' + # 1-100 durability. + # Omit or set to 100 if not applicable + Durability: '100' + # The quantity received upon crafting + Amount: 1 + # Display name with color codes. + # Omit or set to none if not needed + Name: '&4&lCursed Pick' + # Lore for the item, can be multiple lines. + # Use [] if not needed + Lore: + - '&c&lThe Cursed Pickaxe' + # List of enchantments. + # Use [] if not needed + Enchantments: + - 'DURABILITY:255' + # Should enchantments be hidden? + Hide-Enchants: true + # For texture packs, available in MC 1.14+ + # Omit or set to none if not needed + Custom-Model-Data: none + # Add item flags, e.g., to hide attributes + # Use [] if not needed + Item-Flags: [] + # Specify attributes to add + # Format: ATTRIBUTE:AMOUNT:SLOT + # Use [] if not needed + Attribute: [] + # Add specific NBT tags if needed + # Use [] if not needed + Custom-Tags: [] # Potion effects format: "EFFECT:DURATION:AMPLIFIER" # Use [] if not needed Effects: - - 'POISON:20:2' - - # Should enchantments be hidden? - Hide-Enchants: true - - # List of enchantments. - # Use [] if not needed - Enchantments: - - 'DURABILITY:255' + - 'POISON:20:2' # List of commands to run # Should the output item still be granted? @@ -153,23 +165,6 @@ CursedPick: - 'minecraft:adventure/adventuring_time' moon_phase: [0, 4] # 0 = full moon, 4 = new moon [0-7] - # Add specific NBT tags if needed - # Use [] if not needed - Custom-Tags: [] - - # Add item flags, e.g., to hide attributes - # Use [] if not needed - Item-Flags: [] - - # Specify attributes to add - # Format: ATTRIBUTE:AMOUNT:SLOT - # Use [] if not needed - Attribute: [] - - # For texture packs, available in MC 1.14+ - # Omit or set to none if not needed - Custom-Model-Data: none - # Disable crafting in specific worlds # Use [] if not needed Disabled-Worlds: [] \ No newline at end of file diff --git a/resources/recipes/CursedSword.yml b/resources/recipes/CursedSword.yml index d054007..6e659dd 100644 --- a/resources/recipes/CursedSword.yml +++ b/resources/recipes/CursedSword.yml @@ -1,10 +1,8 @@ CursedSword: Enabled: true Shapeless: true + Group: '' Cooldown: -1 - Item: 'DIAMOND_SWORD' - Item-Damage: 'none' - Amount: 1 Placeable: true Exact-Choice: false Custom-Tagged: true @@ -13,14 +11,23 @@ CursedSword: Permission: 'crecipe.recipe.cursedsword' Auto-Discover-Recipe: true Book-Category: 'MISC' - Name: '&eCursed Sword' - Lore: - - '&fThe Cursed Sword' + Result: + Item: 'DIAMOND_SWORD' + Item-Damage: 'none' + Durability: '100' + Amount: 1 + Name: '&eCursed Sword' + Lore: + - '&fThe Cursed Sword' + Enchantments: + - 'DURABILITY:255' + Hide-Enchants: true + Custom-Model-Data: none + Item-Flags: [] + Attribute: [] + Custom-Tags: [] Effects: - 'POISON:20:2' - Hide-Enchants: true - Enchantments: - - 'DURABILITY:255' Commands: Give-Item: true Run-Commands: [] @@ -50,9 +57,5 @@ CursedSword: Ignore-Name: false Use-Conditions: false Conditions_All: [] - Custom-Tags: [] - Item-Flags: [] - Attribute: [] - Custom-Model-Data: none Disabled-Worlds: [] \ No newline at end of file diff --git a/resources/recipes/WheatSand.yml b/resources/recipes/WheatSand.yml index f9a8ab6..667a8dd 100644 --- a/resources/recipes/WheatSand.yml +++ b/resources/recipes/WheatSand.yml @@ -2,13 +2,14 @@ WheatSand: Enabled: true Exact-Choice: true Auto-Discover-Recipe: true - Book-Category: 'MISC' - Item: SAND - Amount: 1 Identifier: WHEATSAND Converter: FURNACE + Book-Category: 'MISC' Cooktime: 20 # In ticks - 20 ticks per second Experience: 1 + Result: + Item: SAND + Amount: 1 ItemCrafting: - WXX - XXX @@ -18,7 +19,4 @@ WheatSand: Material: WHEAT_SEEDS Identifier: none Amount: 1 - Item-Flags: [] - Attribute: [] - Disabled-Worlds: [] - Custom-Tags: [] \ No newline at end of file + Disabled-Worlds: [] \ No newline at end of file diff --git a/src/me/mehboss/anvil/AnvilManager.java b/src/me/mehboss/anvil/AnvilManager.java index 1f2c670..33c435e 100644 --- a/src/me/mehboss/anvil/AnvilManager.java +++ b/src/me/mehboss/anvil/AnvilManager.java @@ -13,25 +13,46 @@ import org.bukkit.inventory.ItemStack; import me.mehboss.recipe.Main; +import me.mehboss.utils.CompatibilityUtil; import me.mehboss.utils.RecipeUtil; import me.mehboss.utils.RecipeUtil.Ingredient; import me.mehboss.utils.RecipeUtil.Recipe; import me.mehboss.utils.RecipeUtil.Recipe.RecipeType; - +import me.mehboss.utils.data.WorkstationRecipeData; + +/** + * Handles custom anvil crafting logic for recipes registered via RecipeUtil. + *

    + * This class listens for {@link PrepareAnvilEvent} and attempts to match the + * anvil's current input items to any custom anvil-type recipe. If a match is + * found and the player has the required permissions, the output result and + * repair cost are applied to the anvil. + */ public class AnvilManager implements Listener { public HashMap runTimes = new HashMap(); + /** + * Handles anvil preparation events and checks if the items inside the anvil + * inputs match any custom ANVIL-type recipe. + * + * @param e The PrepareAnvilEvent fired by Bukkit. + */ @EventHandler - public void onPlace(PrepareAnvilEvent e) { + void onPlace(PrepareAnvilEvent e) { + + if (Main.getInstance().serverVersionLessThan(1, 9)) + return; + AnvilInventory inv = e.getInventory(); Recipe matchedRecipe = null; - Player p = (Player) e.getView().getPlayer(); + Player p = (Player) CompatibilityUtil.getPlayerFromView(CompatibilityUtil.getInventoryView(e)); - if (getRecipeUtil().getAllRecipes() == null) + HashMap anvilRecipes = getRecipeUtil().getRecipesFromType(RecipeType.ANVIL); + if (anvilRecipes == null || anvilRecipes.isEmpty()) return; - for (Recipe recipe : getRecipeUtil().getAllRecipes().values()) { + for (Recipe recipe : anvilRecipes.values()) { Boolean matchedToRecipe = true; if (recipe.getType() != RecipeType.ANVIL) { @@ -65,13 +86,24 @@ public void onPlace(PrepareAnvilEvent e) { return; } - logDebug(matchedRecipe.getName() + ": Requirements met.. Successfully set anvil result slot"); - e.setResult(matchedRecipe.getResult()); - inv.setRepairCost(matchedRecipe.getRepairCost()); + WorkstationRecipeData anvil = (WorkstationRecipeData) matchedRecipe; + + logDebug(anvil.getName() + ": Requirements met.. Successfully set anvil result slot"); + e.setResult(anvil.getResult()); + inv.setRepairCost(anvil.getRepairCost()); p.updateInventory(); } } + /** + * Checks whether the provided item satisfies the ingredient's required amount. + * + * @param recipeName Name of the recipe (used for debugging messages). + * @param item ItemStack inside the anvil. + * @param ingredient Ingredient definition being compared to. + * @return true if the item meets or exceeds the amount required; false + * otherwise. + */ Boolean amountsMatch(String recipeName, ItemStack item, Ingredient ingredient) { if (item == null || ingredient == null) { logDebug(recipeName + ": Item or Ingredient is null"); @@ -91,9 +123,18 @@ Boolean amountsMatch(String recipeName, ItemStack item, Ingredient ingredient) { } RecipeUtil getRecipeUtil() { - return Main.getInstance().recipeUtil; + return Main.getInstance().recipeUtil; } - + + /** + * Checks if the given ItemStack matches the ingredient's item type and + * metadata. + * + * @param recipeName Name of the recipe (used for debug logging). + * @param item ItemStack from the anvil input. + * @param ingredient Ingredient to compare against. + * @return true if items match according to MetaChecks; false otherwise. + */ boolean itemsMatch(String recipeName, ItemStack item, Ingredient ingredient) { return Main.getInstance().metaChecks.itemsMatch(recipeName, item, ingredient); } diff --git a/src/me/mehboss/anvil/AnvilManager_1_8.java b/src/me/mehboss/anvil/AnvilManager_1_8.java new file mode 100644 index 0000000..6eb73f7 --- /dev/null +++ b/src/me/mehboss/anvil/AnvilManager_1_8.java @@ -0,0 +1,145 @@ +package me.mehboss.anvil; + +import java.util.HashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.AnvilInventory; +import org.bukkit.inventory.ItemStack; + +import me.mehboss.recipe.Main; +import me.mehboss.utils.RecipeUtil; +import me.mehboss.utils.RecipeUtil.Ingredient; +import me.mehboss.utils.RecipeUtil.Recipe; +import me.mehboss.utils.RecipeUtil.Recipe.RecipeType; +import me.mehboss.utils.data.WorkstationRecipeData; + +public class AnvilManager_1_8 implements Listener { + + @EventHandler + void onPlace(InventoryClickEvent e) { + + // Only run in an anvil + if (!(e.getInventory() instanceof AnvilInventory)) + return; + + if (Main.getInstance().serverVersionAtLeast(1, 9)) + return; + + // Only respond to clicks inside the anvil input/result slots + if (e.getRawSlot() > 2) + return; + + Player p = (Player) e.getWhoClicked(); + AnvilInventory inv = (AnvilInventory) e.getInventory(); + + // Delay by 1 tick so the inventory updates like PrepareAnvilEvent would + Bukkit.getScheduler().runTask(Main.getInstance(), () -> { + + Recipe matchedRecipe = null; + HashMap anvilRecipes = getRecipeUtil().getRecipesFromType(RecipeType.ANVIL); + + if (anvilRecipes == null || anvilRecipes.isEmpty()) + return; + + for (Recipe recipe : anvilRecipes.values()) { + Boolean matchedToRecipe = true; + + int slot = 0; + for (Ingredient ingredient : recipe.getIngredients()) { + if (!itemsMatch(recipe.getName(), inv.getItem(slot), ingredient) + || !amountsMatch(recipe.getName(), inv.getItem(slot), ingredient)) { + matchedToRecipe = false; + break; + } + slot++; + } + + if (matchedToRecipe) { + matchedRecipe = recipe; + break; + } + + logDebug(recipe.getName() + ": Requirements not met.. Skipping to next recipe"); + } + + if (matchedRecipe != null) { + if (!matchedRecipe.isActive() + || (matchedRecipe.getPerm() != null && !p.hasPermission(matchedRecipe.getPerm())) + || matchedRecipe.getDisabledWorlds().contains(p.getWorld().getName())) { + sendNoPermsMessage(p, matchedRecipe.getName()); + inv.setItem(2, null); + return; + } + + WorkstationRecipeData anvil = (WorkstationRecipeData) matchedRecipe; + + logDebug(anvil.getName() + ": Requirements met.. Successfully set anvil result slot"); + + inv.setItem(2, anvil.getResult()); // prepareAnvilEvent#setResult equivalent + inv.setRepairCost(anvil.getRepairCost()); + p.updateInventory(); + } + }); + } + + /** + * Checks whether the provided item satisfies the ingredient's required amount. + * + * @param recipeName Name of the recipe (used for debugging messages). + * @param item ItemStack inside the anvil. + * @param ingredient Ingredient definition being compared to. + * @return true if the item meets or exceeds the amount required; false + * otherwise. + */ + Boolean amountsMatch(String recipeName, ItemStack item, Ingredient ingredient) { + if (item == null || ingredient == null) { + logDebug(recipeName + ": Item or Ingredient is null"); + logDebug(recipeName + ": Item - " + item); + logDebug(recipeName + ": Ingredient - " + ingredient); + return false; + } + + if (item.getAmount() < ingredient.getAmount()) { + logDebug(recipeName + ": Amount requirements not met"); + logDebug(recipeName + ": Slot amount - " + item.getAmount()); + logDebug(recipeName + ": Ingredient amount - " + ingredient.getAmount()); + return false; + } + + return true; + } + + RecipeUtil getRecipeUtil() { + return Main.getInstance().recipeUtil; + } + + /** + * Checks if the given ItemStack matches the ingredient's item type and + * metadata. + * + * @param recipeName Name of the recipe (used for debug logging). + * @param item ItemStack from the anvil input. + * @param ingredient Ingredient to compare against. + * @return true if items match according to MetaChecks; false otherwise. + */ + boolean itemsMatch(String recipeName, ItemStack item, Ingredient ingredient) { + return Main.getInstance().metaChecks.itemsMatch(recipeName, item, ingredient); + } + + void logDebug(String st) { + if (Main.getInstance().debug) + Logger.getLogger("Minecraft").log(Level.WARNING, "[DEBUG][" + Main.getInstance().getName() + "] " + st); + } + + void sendNoPermsMessage(Player p, String recipe) { + logDebug("[sendNoPermsMessage] Player " + p.getName() + + " does not have required recipe crafting permissions for recipe " + recipe); + Main.getInstance().sendnoPerms(p); + } +} diff --git a/src/me/mehboss/anvil/GrindstoneManager.java b/src/me/mehboss/anvil/GrindstoneManager.java index 4af115a..296188c 100644 --- a/src/me/mehboss/anvil/GrindstoneManager.java +++ b/src/me/mehboss/anvil/GrindstoneManager.java @@ -33,7 +33,16 @@ import me.mehboss.utils.RecipeUtil.Ingredient; import me.mehboss.utils.RecipeUtil.Recipe; import me.mehboss.utils.RecipeUtil.Recipe.RecipeType; - +import me.mehboss.utils.data.WorkstationRecipeData; + +/** + * Handles all custom logic for grindstone-based workstation recipes. + *

    + * This listener monitors grindstone input changes, result collection actions, + * and drag events to allow players to craft custom grindstone recipes registered + * via {@link RecipeUtil}. The class also handles permission checks, XP rewards, + * ingredient consumption, and result previewing. + */ public class GrindstoneManager implements Listener { private static final Map matchedByPlayer = new HashMap<>(); @@ -41,8 +50,21 @@ RecipeUtil getRecipeUtil() { return Main.getInstance().recipeUtil; } + /** + * Handles all result-collection actions inside a grindstone. This includes: + *

      + *
    • Detecting whether the click is a valid take action
    • + *
    • Ensuring custom output is only given when a recipe was matched
    • + *
    • Handling cursor stacking and shift-clicking
    • + *
    • Playing grindstone sound effects
    • + *
    • Giving experience (if configured)
    • + *
    • Consuming recipe ingredients
    • + *
    + * + * @param event The InventoryClickEvent triggered by a player. + */ @EventHandler(priority = EventPriority.HIGHEST) - public void onCollectResult(InventoryClickEvent event) { + void onCollectResult(InventoryClickEvent event) { if (event.getClickedInventory() == null || event.getAction() == InventoryAction.NOTHING || event.getClickedInventory().getType() != InventoryType.GRINDSTONE) { return; @@ -137,19 +159,38 @@ public void onCollectResult(InventoryClickEvent event) { Bukkit.getScheduler().runTask(Main.getInstance(), player::updateInventory); } + + /** + * Prevents dragging into grindstone slots, ensuring only controlled click + * interactions can place or remove items. + * + * @param event The InventoryDragEvent triggered by a drag action. + */ @EventHandler(ignoreCancelled = true) - public void onDrag(InventoryDragEvent event) { + void onDrag(InventoryDragEvent event) { if (event.getInventory().getType() != InventoryType.GRINDSTONE || event.getInventorySlots().isEmpty()) return; InventoryView view = event.getView(); - // If any dragged raw slot is the grindstone, cancel (matches your original) if (event.getRawSlots().stream().anyMatch(i -> view.getInventory(i) instanceof GrindstoneInventory)) { event.setCancelled(true); } } - private void processGrindstone(Inventory inv, Player player, InventoryInteractEvent event) { + /** + * Core logic to determine whether current grindstone inputs match a custom recipe. + * Handles: + *
      + *
    • Clearing the result slot when no longer matching
    • + *
    • Displaying the custom output if matched
    • + *
    • Performing permission & world-disabled checks
    • + *
    + * + * @param inv Grindstone inventory being processed. + * @param player Player using the grindstone. + * @param event Inventory event that triggered this update. + */ + void processGrindstone(Inventory inv, Player player, InventoryInteractEvent event) { if (!(inv instanceof GrindstoneInventory)) return; @@ -195,7 +236,16 @@ private void processGrindstone(Inventory inv, Player player, InventoryInteractEv } } - private Optional findMatch(ItemStack top, ItemStack bottom, Player p, InventoryView view) { + /** + * Attempts to match two grindstone input items with a registered custom recipe. + * + * @param top Item in slot 0. + * @param bottom Item in slot 1. + * @param p Player interacting with the grindstone. + * @param view InventoryView representing the UI state. + * @return Optional containing a matching Recipe or empty. + */ + Optional findMatch(ItemStack top, ItemStack bottom, Player p, InventoryView view) { if (getRecipeUtil().getAllRecipes() == null) return Optional.empty(); @@ -241,15 +291,24 @@ else if (bottomIng == null) return Optional.empty(); } + /** Returns a safe result ItemStack for recipe output (never null). */ private ItemStack safeResultOf(Recipe recipe) { ItemStack res = recipe.getResult(); return res == null ? new ItemStack(Material.AIR) : res.clone(); } + /** Clears output slot (slot 2) of a grindstone. */ private void clearResultSlot(GrindstoneInventory inv) { inv.setItem(2, new ItemStack(Material.AIR)); } + /** + * Consumes ingredients from the grindstone input slots according to recipe needs. + * + * @param inv Grindstone inventory used. + * @param recipe Recipe whose ingredients will be consumed. + * @param p Player performing the craft. + */ private void consumeIngredients(GrindstoneInventory inv, Recipe recipe, Player p) { Ingredient topIng = null; @@ -271,6 +330,7 @@ else if (bottomIng == null) shrinkSlot(inv, 1, bottomIng, p); } + /** Reduces item count in a grindstone input slot or clears it if empty. */ private void shrinkSlot(GrindstoneInventory inv, int slot, Ingredient ing, Player p) { ItemStack stack = inv.getItem(slot); if (stack == null) @@ -287,18 +347,20 @@ private void shrinkSlot(GrindstoneInventory inv, int slot, Ingredient ing, Playe } } - private void tryGiveXp(Player p, Recipe recipe) { - - if (recipe.getExperience() <= 0) + /** Gives the player experience based on the recipe configuration. */ + private void tryGiveXp(Player p, Recipe rawRecipe) { + WorkstationRecipeData matched = (WorkstationRecipeData) rawRecipe; + if (matched.getExperience() <= 0) return; - int xp = (int) recipe.getExperience(); + int xp = (int) matched.getExperience(); if (xp > 0) { ExperienceOrb orb = p.getWorld().spawn(p.getLocation(), ExperienceOrb.class); orb.setExperience(xp); } } + /** Checks ingredient amount requirements. */ private boolean amountsMatch(String recipeName, ItemStack item, Ingredient ingredient) { if (item == null || ingredient == null) { logDebug("Item or Ingredient is null", recipeName); @@ -315,6 +377,7 @@ private boolean amountsMatch(String recipeName, ItemStack item, Ingredient ingre return true; } + /** Checks item similarity using metadata comparison handlers. */ private boolean itemsMatch(String recipeName, ItemStack item, Ingredient ingredient) { return Main.getInstance().metaChecks.itemsMatch(recipeName, item, ingredient); } diff --git a/src/me/mehboss/anvil/package-info.java b/src/me/mehboss/anvil/package-info.java new file mode 100644 index 0000000..63b463a --- /dev/null +++ b/src/me/mehboss/anvil/package-info.java @@ -0,0 +1,7 @@ +/** + * Contains all logic related to custom anvil and workstation crafting. + *

    + * Includes event listeners, recipe matching, and output logic used to + * override or extend vanilla anvil behavior. + */ +package me.mehboss.anvil; \ No newline at end of file diff --git a/src/me/mehboss/brewing/BrewAction.java b/src/me/mehboss/brewing/BrewAction.java new file mode 100644 index 0000000..1f49dcf --- /dev/null +++ b/src/me/mehboss/brewing/BrewAction.java @@ -0,0 +1,20 @@ +package me.mehboss.brewing; + +import org.bukkit.inventory.BrewerInventory; +import org.bukkit.inventory.ItemStack; + +/** + * Represents a brewing action to perform when brewing completes. + * Extend this class to define custom behavior for each brewing recipe. + */ +public abstract class BrewAction { + + /** + * Called when brewing completes. + * + * @param inventory the brewing stand inventory + * @param item the potion/output slot item + * @param ingredient the ingredient used for brewing + */ + public abstract void brew(BrewerInventory inventory, ItemStack item, ItemStack ingredient); +} \ No newline at end of file diff --git a/src/me/mehboss/brewing/BrewEvent.java b/src/me/mehboss/brewing/BrewEvent.java new file mode 100644 index 0000000..873e757 --- /dev/null +++ b/src/me/mehboss/brewing/BrewEvent.java @@ -0,0 +1,157 @@ +package me.mehboss.brewing; + +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.BrewerInventory; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import me.mehboss.recipe.Main; +import me.mehboss.utils.data.BrewingRecipeData; + +/** + * Handles custom brewing logic: - Allows non-standard items in brewing stands - + * Prevents item disappearance with shift-clicks - Automatically starts brewing + * when valid custom recipes are present - Supports both empty-slot and + * bottle-required brewing + */ +public class BrewEvent implements Listener { + + @EventHandler + void onCustomPotionClick(InventoryClickEvent event) { + Inventory inv = event.getClickedInventory(); + if (inv == null || inv.getType() != InventoryType.BREWING) + return; + if (!(inv instanceof BrewerInventory)) + return; + + BrewerInventory brewer = (BrewerInventory) inv; + Player player = (Player) event.getWhoClicked(); + int slot = event.getRawSlot(); + + if (event.getClick() == ClickType.SHIFT_LEFT || event.getClick() == ClickType.SHIFT_RIGHT) { + Bukkit.getScheduler().runTaskLater(Main.getInstance(), () -> { + if (brewer.getHolder().getBrewingTime() > 0) + return; + + BrewingRecipeData recipe = BrewingRecipe.getRecipe(brewer); + if (recipe == null) + return; + + if (!canBrewIntoResultSlots(brewer, recipe)) + return; + + BrewingRecipe.startBrewing(brewer, recipe); + }, 2L); + return; + } + + // --- Regular left/right click handling --- + if (!(event.getClick() == ClickType.LEFT || event.getClick() == ClickType.RIGHT)) + return; + + event.setCancelled(true); // override vanilla behavior safely + + ItemStack clicked = event.getCurrentItem(); + ItemStack cursor = event.getCursor(); + + if (clicked == null) + clicked = new ItemStack(Material.AIR); + if (cursor == null) + cursor = new ItemStack(Material.AIR); + + boolean compare = clicked.isSimilar(cursor); + ClickType clickType = event.getClick(); + int firstAmount = clicked.getAmount(); + int secondAmount = cursor.getAmount(); + int stackMax = clicked.getMaxStackSize(); + + // --- Manual placement logic --- + if (clickType == ClickType.LEFT) { + if (clicked.getType() == Material.AIR) { + inv.setItem(slot, cursor.clone()); + player.setItemOnCursor(null); + } else if (compare) { + int canAdd = stackMax - firstAmount; + if (secondAmount <= canAdd) { + clicked.setAmount(firstAmount + secondAmount); + player.setItemOnCursor(null); + } else { + cursor.setAmount(secondAmount - canAdd); + clicked.setAmount(stackMax); + player.setItemOnCursor(cursor); + } + } else { + inv.setItem(slot, cursor.clone()); + player.setItemOnCursor(clicked.clone()); + } + } else if (clickType == ClickType.RIGHT) { + if (clicked.getType() == Material.AIR) { + ItemStack one = cursor.clone(); + one.setAmount(1); + inv.setItem(slot, one); + cursor.setAmount(secondAmount - 1); + if (cursor.getAmount() <= 0) + player.setItemOnCursor(null); + else + player.setItemOnCursor(cursor); + } else if (compare) { + if (firstAmount + 1 <= stackMax) { + clicked.setAmount(firstAmount + 1); + cursor.setAmount(secondAmount - 1); + if (cursor.getAmount() <= 0) + player.setItemOnCursor(null); + } + } else { + inv.setItem(slot, cursor.clone()); + player.setItemOnCursor(clicked.clone()); + } + } + + // --- Run brew check after inventory updates (next tick) --- + Bukkit.getScheduler().runTaskLater(Main.getInstance(), () -> { + + // If stand is already brewing, don't restart it + if (brewer.getHolder().getBrewingTime() > 0) + return; + + // Lookup recipe (now that slots are updated) + BrewingRecipeData recipe = BrewingRecipe.getRecipe(brewer); + if (recipe == null) + return; + + // Validate bottle/output slots before brewing + if (!canBrewIntoResultSlots(brewer, recipe)) + return; + + BrewingRecipe.startBrewing(brewer, recipe); + }, 2L); + } + + /** + * Validates that there’s at least one valid bottle slot or empty slot depending + * on whether the recipe requires bottles. + */ + private boolean canBrewIntoResultSlots(BrewerInventory inventory, BrewingRecipeData recipe) { + boolean requiresBottles = recipe.requiresBottles(); + Material requiredBottle = recipe.getRequiredBottleType(); + + for (int i = 0; i < 3; i++) { + ItemStack item = inventory.getItem(i); + if (!requiresBottles) { + if (item == null || item.getType() == Material.AIR) + return true; + } else { + if (item != null && item.getType() == requiredBottle) + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/src/me/mehboss/brewing/Brewery.java b/src/me/mehboss/brewing/Brewery.java new file mode 100644 index 0000000..b5dbb3c --- /dev/null +++ b/src/me/mehboss/brewing/Brewery.java @@ -0,0 +1,74 @@ +package me.mehboss.brewing; + +import org.bukkit.Bukkit; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.BrewerInventory; +import org.bukkit.plugin.java.JavaPlugin; + +import me.mehboss.utils.data.BrewingRecipeData; + +/** + * Handles the registration and execution of custom brewing recipes. + *

    + * This class acts as the core manager for custom brewing logic by: + *

      + *
    • Maintaining a static singleton reference for recipe access
    • + *
    • Listening for brewing stand inventory interactions
    • + *
    • Triggering custom brewing behavior when recipe inputs match
    • + *
    + * Custom brewing recipes are retrieved through {@link BrewingRecipe#getRecipe(BrewerInventory)} + * and executed using {@link BrewingRecipe#startBrewing(BrewerInventory, BrewingRecipeData)}. + */ +public class Brewery implements Listener { + + private static Brewery instance; + private final JavaPlugin plugin; + + /** + * Creates a new Brewery manager and registers it as an event listener. + * + * @param plugin The plugin instance that owns this brewing system. + */ + public Brewery(JavaPlugin plugin) { + this.plugin = plugin; + instance = this; + Bukkit.getPluginManager().registerEvents(this, plugin); + } + + public static Brewery getInstance() { + return instance; + } + + public JavaPlugin getPlugin() { + return plugin; + } + + /** + * Intercepts brewing stand clicks and evaluates whether custom brewing should begin. + *

    + * This event handler: + *

      + *
    • Ensures interaction occurred inside a brewing stand inventory
    • + *
    • Schedules a short delayed task (2 ticks) to allow item placement to finalize
    • + *
    • Attempts to match inputs to a {@link BrewingRecipeData}
    • + *
    • If matched, starts custom brewing via {@link BrewingRecipe#startBrewing}
    • + *
    + * + * @param event The InventoryClickEvent triggered when a player interacts with an inventory. + */ + @EventHandler + public void onBrewClick(InventoryClickEvent event) { + if (event.getClickedInventory() == null || event.getClickedInventory().getType() != InventoryType.BREWING) + return; + + BrewerInventory inv = (BrewerInventory) event.getClickedInventory(); + Bukkit.getScheduler().runTaskLater(plugin, () -> { + BrewingRecipeData recipe = BrewingRecipe.getRecipe(inv); + if (recipe != null) + BrewingRecipe.startBrewing(inv, recipe); + }, 2L); + } +} \ No newline at end of file diff --git a/src/me/mehboss/brewing/BrewingRecipe.java b/src/me/mehboss/brewing/BrewingRecipe.java new file mode 100644 index 0000000..e450f74 --- /dev/null +++ b/src/me/mehboss/brewing/BrewingRecipe.java @@ -0,0 +1,331 @@ +package me.mehboss.brewing; + +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.BrewingStand; +import org.bukkit.entity.Player; +import org.bukkit.inventory.BrewerInventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.scheduler.BukkitRunnable; + +import com.cryptomorin.xseries.XSound; + +import de.tr7zw.changeme.nbtapi.NBTTileEntity; +import me.mehboss.recipe.Main; +import me.mehboss.utils.RecipeUtil; +import me.mehboss.utils.RecipeUtil.Recipe; +import me.mehboss.utils.RecipeUtil.Recipe.RecipeType; +import me.mehboss.utils.data.BrewingRecipeData; + +/** + * Handles runtime logic for custom brewing recipes. + *

    + * This class: + *

      + *
    • Locates matching brewing recipes based on inventory contents
    • + *
    • Starts and manages brewing timers
    • + *
    • Consumes fuel and ingredients
    • + *
    • Controls progress via NBT BrewTime
    • + *
    • Applies custom brewing results
    • + *
    + * + *

    Note: This class does not store recipe definitions. All + * recipe data is stored within {@link BrewingRecipeData} via RecipeUtil. + */ +public class BrewingRecipe { + + + /** + * Attempts to locate a brewing recipe that matches the given brewer inventory. + *

    + * Matching criteria: + *

      + *
    • Ingredient slot item matches recipe ingredient
    • + *
    • Fuel slot item matches recipe fuel
    • + *
    • Recipe is active
    • + *
    • Player has permission (if required)
    • + *
    • World is not disabled
    • + *
    + * + * @param inventory The brewing stand inventory to match. + * @return A matching {@link BrewingRecipeData}, or {@code null} if none match. + */ + public static BrewingRecipeData getRecipe(BrewerInventory inventory) { + RecipeUtil recipeUtil = Main.getInstance().getRecipeUtil(); + Map recipes = recipeUtil.getRecipesFromType(RecipeType.BREWING_STAND); + if (recipes == null || recipes.isEmpty()) return null; + + for (Recipe base : recipes.values()) { + if (!(base instanceof BrewingRecipeData)) continue; + BrewingRecipeData recipe = (BrewingRecipeData) base; + + ItemStack ingredient = inventory.getItem(recipe.getBrewIngredientSlot()); + ItemStack fuel = inventory.getItem(recipe.getBrewFuelSlot()); + + boolean ingredientMatch = recipe.isBrewPerfect() + ? (ingredient != null && ingredient.isSimilar(recipe.getBrewIngredient())) + : (ingredient != null && ingredient.getType() == recipe.getBrewIngredient().getType()); + + boolean fuelMatch = recipe.isBrewPerfect() + ? (fuel != null && fuel.isSimilar(recipe.getBrewFuel())) + : (fuel != null && fuel.getType() == recipe.getBrewFuel().getType()); + + Player p = null; + World w = inventory.getLocation().getWorld(); + + if (inventory.getHolder() != null && inventory.getHolder() instanceof Player) + p = (Player) inventory.getHolder(); + + boolean hasPerms = p == null || !recipe.hasPerm() || p.hasPermission(recipe.getPerm()); + boolean allowWorld = w == null || !recipe.getDisabledWorlds().contains(w.getName()); + + if (ingredientMatch && fuelMatch && recipe.isActive() && hasPerms && allowWorld) + return recipe; + } + + return null; + } + + /** + * Begins a brewing cycle using the provided recipe. + *

    + * The default brewing time is 400 ticks (20 seconds), unless overridden in + * {@link BrewClock}. + * + *

    This method: + *

      + *
    • Consumes fuel if internal fuel tank is empty
    • + *
    • Initializes a {@link BrewClock} to track progress
    • + *
    + * + * @param inventory Brewer inventory involved in brewing. + * @param recipe The recipe to brew. + */ + public static void startBrewing(BrewerInventory inventory, BrewingRecipeData recipe) { + BrewingStand stand = inventory.getHolder(); + if (stand == null) return; + + NBTTileEntity nbt = new NBTTileEntity(stand); + int currentFuel = nbt.getInteger("Fuel"); + + // Only consume fuel item if internal fuel tank is empty + if (currentFuel <= 0) { + ItemStack fuelItem = inventory.getItem(recipe.getBrewFuelSlot()); + if (fuelItem != null && fuelItem.getType() != Material.AIR && fuelItem.getAmount() > 0) { + // Add internal fuel and consume one fuel item + nbt.setInteger("Fuel", recipe.getBrewFuelSet()); + + int newAmount = fuelItem.getAmount() - 1; + if (newAmount <= 0) + inventory.setItem(recipe.getBrewFuelSlot(), new ItemStack(Material.AIR)); + else { + fuelItem.setAmount(newAmount); + inventory.setItem(recipe.getBrewFuelSlot(), fuelItem); + } + } else { + return; // No fuel available + } + } + + // Begin the brew process + new BrewClock(inventory, recipe, 400); + } + + + /** + * Handles the ticking and completion of a brewing cycle. + *

    + * This class: + *

      + *
    • Counts down BrewTime via NBT
    • + *
    • Checks for missing ingredients/fuel mid-brew
    • + *
    • Consumes internal tank fuel
    • + *
    • Consumes recipe ingredients
    • + *
    • Applies recipe results into potion bottle slots
    • + *
    • Automatically triggers the next brewing cycle if conditions allow
    • + *
    + */ + private static class BrewClock extends BukkitRunnable { + private final BrewerInventory inventory; + private final BrewingRecipeData recipe; + private int ticksRemaining; + + /** + * Creates and starts a brewing timer. + * + * @param inventory The brewing stand inventory. + * @param recipe The recipe being brewed. + * @param brewTimeTicks Total ticks for the brewing cycle (typically 400). + */ + public BrewClock(BrewerInventory inventory, BrewingRecipeData recipe, int brewTimeTicks) { + this.inventory = inventory; + this.recipe = recipe; + this.ticksRemaining = brewTimeTicks; + runTaskTimer(Main.getInstance(), 0L, 1L); + } + + @Override + public void run() { + BrewingStand stand = inventory.getHolder(); + if (stand == null || stand.getBlock().getType() != Material.BREWING_STAND) { + cancel(); + return; + } + + if (ingredientOrFuelGone()) { + setBrewTimeNBT(stand, 0); + cancel(); + return; + } + + if (ticksRemaining <= 0) { + cancel(); + finishOneBrew(stand); + return; + } + + setBrewTimeNBT(stand, ticksRemaining); + ticksRemaining--; + } + + /** + * Completes a single brewing cycle. + * Handles: + *
      + *
    • Fuel drain / consumption
    • + *
    • Ingredient consumption
    • + *
    • Result placement
    • + *
    • Sound effects
    • + *
    • Automatic continuation
    • + *
    + * + * @param stand The brewing stand completing its brew. + */ + private void finishOneBrew(BrewingStand stand) { + ItemStack ingredient = inventory.getItem(recipe.getBrewIngredientSlot()); + if (ingredient == null || ingredient.getType() == Material.AIR) + return; + + // --- Fuel Management --- + NBTTileEntity standNBT = new NBTTileEntity(stand); + int currentFuel = standNBT.getInteger("Fuel"); + int requiredCharge = recipe.getBrewFuelCharge(); + + // If not enough internal fuel left, consume another fuel item + if (currentFuel < requiredCharge) { + ItemStack fuel = inventory.getItem(recipe.getBrewFuelSlot()); + if (fuel != null && fuel.getType() != Material.AIR && fuel.getAmount() > 0) { + int newAmount = fuel.getAmount() - 1; + if (newAmount <= 0) + inventory.setItem(recipe.getBrewFuelSlot(), new ItemStack(Material.AIR)); + else { + fuel.setAmount(newAmount); + inventory.setItem(recipe.getBrewFuelSlot(), fuel); + } + currentFuel += recipe.getBrewFuelSet(); + } else { + standNBT.setInteger("Fuel", 0); + return; + } + } + + // Drain internal fuel + int newFuel = Math.max(0, currentFuel - requiredCharge); + standNBT.setInteger("Fuel", newFuel); + + // --- Ingredient consumption --- + int newIngAmount = ingredient.getAmount() - 1; + if (newIngAmount <= 0) + inventory.setItem(recipe.getBrewIngredientSlot(), new ItemStack(Material.AIR)); + else { + ingredient.setAmount(newIngAmount); + inventory.setItem(recipe.getBrewIngredientSlot(), ingredient); + } + + // --- Apply results --- + ItemStack result = recipe.getResult(); + if (result != null && result.getType() != Material.AIR) { + boolean requiresBottles = recipe.requiresBottles(); + Material requiredBottle = recipe.getRequiredBottleType(); + + for (int i = 0; i < 3; i++) { + ItemStack base = inventory.getItem(i); + if (!requiresBottles) { + if (base == null || base.getType() == Material.AIR) + inventory.setItem(i, result.clone()); + } else if (base != null && base.getType() == requiredBottle) { + inventory.setItem(i, result.clone()); + } + } + } + + // --- Visual / Sound --- + Bukkit.getScheduler().runTaskLater(Main.getInstance(), () -> { + setBrewTimeNBT(stand, 0); + stand.getWorld().playSound(stand.getLocation(), XSound.BLOCK_BREWING_STAND_BREW.parseSound(), 1f, 1f); + }, 1L); + + // --- Continue automatically --- + if (canContinue()) + startBrewing(inventory, recipe); + } + + /** @return true if brewing can continue into another automatic cycle. */ + private boolean canContinue() { + ItemStack ing = inventory.getItem(recipe.getBrewIngredientSlot()); + ItemStack fuel = inventory.getItem(recipe.getBrewFuelSlot()); + return ing != null && ing.getType() != Material.AIR && + fuel != null && fuel.getType() != Material.AIR && + hasValidResultSlot(); + } + + /** + * Checks whether there is at least one available or valid bottle slot. + */ + private boolean hasValidResultSlot() { + boolean requiresBottles = recipe.requiresBottles(); + Material requiredBottle = recipe.getRequiredBottleType(); + for (int i = 0; i < 3; i++) { + ItemStack item = inventory.getItem(i); + if (!requiresBottles) { + if (item == null || item.getType() == Material.AIR) + return true; + } else if (item != null && item.getType() == requiredBottle) { + return true; + } + } + return false; + } + + /** @return true if either ingredient or fuel is missing. */ + private boolean ingredientOrFuelGone() { + ItemStack ing = inventory.getItem(recipe.getBrewIngredientSlot()); + ItemStack fuel = inventory.getItem(recipe.getBrewFuelSlot()); + return ing == null || ing.getType() == Material.AIR || + fuel == null || fuel.getType() == Material.AIR; + } + } + + /** + * Updates the brewing stand's NBT BrewTime value. + * + * @param stand The brewing stand whose BrewTime is being updated. + * @param ticks The number of ticks remaining in the brewing cycle. + */ + private static void setBrewTimeNBT(BrewingStand stand, int ticks) { + try { + new NBTTileEntity(stand).setInteger("BrewTime", ticks); + } catch (Exception e) { + logError("Failed to update BrewTime for stand at " + stand.getLocation()); + } + } + + private static void logError(String msg) { + Logger.getLogger("Minecraft").log(Level.WARNING, "[DEBUG][" + Main.getInstance().getName() + "] " + msg); + } +} \ No newline at end of file diff --git a/src/me/mehboss/brewing/package-info.java b/src/me/mehboss/brewing/package-info.java new file mode 100644 index 0000000..6c4967e --- /dev/null +++ b/src/me/mehboss/brewing/package-info.java @@ -0,0 +1,7 @@ +/** + * Handles custom brewing stand recipes and brewing mechanics. + *

    + * Includes recipe matching, fuel handling, brewing timers, and result + * processing through the BrewingRecipe and BrewClock systems. + */ +package me.mehboss.brewing; \ No newline at end of file diff --git a/src/me/mehboss/commands/CommandEditItem.java b/src/me/mehboss/commands/CommandEditItem.java index 39deb71..0d62a85 100644 --- a/src/me/mehboss/commands/CommandEditItem.java +++ b/src/me/mehboss/commands/CommandEditItem.java @@ -9,8 +9,9 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; -import io.github.bananapuncher714.nbteditor.NBTEditor; -import me.mehboss.gui.RecipeGUI; +import de.tr7zw.changeme.nbtapi.NBT; +import de.tr7zw.changeme.nbtapi.NBTItem; +import me.mehboss.gui.framework.RecipeGUI; import me.mehboss.recipe.Main; public class CommandEditItem { @@ -70,7 +71,9 @@ public static boolean Run(CRCommand command) { if (command.args.length == 3 && command.args[1].equalsIgnoreCase("key")) { ItemStack item = p.getItemInHand(); - item = NBTEditor.set(item, command.args[2], "CUSTOM_ITEM_IDENTIFIER"); + NBT.modify(item, (nbt) -> { + nbt.setString("CUSTOM_ITEM_IDENTIFIER", command.args[2]); + }); p.setItemInHand(item); p.sendMessage(ChatColor.GREEN + "Successfully updated item identifier!"); @@ -161,8 +164,6 @@ public static boolean Run(CRCommand command) { } if (command.args.length == 2 && command.args[1].equalsIgnoreCase("lore")) { - RecipeGUI.getInstance().sendLoremsg(p); - RecipeGUI.getInstance().editmeta.put(p.getUniqueId(), "Lore"); return false; } diff --git a/src/me/mehboss/commands/CommandGive.java b/src/me/mehboss/commands/CommandGive.java index 1179ec2..8089d8d 100644 --- a/src/me/mehboss/commands/CommandGive.java +++ b/src/me/mehboss/commands/CommandGive.java @@ -19,7 +19,7 @@ static FileConfiguration getConfig() { public static boolean Run(CRCommand command) { - Recipe recipe = Main.getInstance().getRecipeUtil().getRecipe(command.args[2]); + Recipe recipe = Main.getInstance().getRecipeUtil().getRecipeFromKey(command.args[2]); CommandSender p = command.sender; int amount = 1; @@ -56,7 +56,7 @@ public static boolean Run(CRCommand command) { // recipe id if (recipe != null) - item = new ItemStack(Main.getInstance().getRecipeUtil().getRecipe(command.args[2]).getResult()); + item = new ItemStack(recipe.getResult()); // custom item id else item = new ItemStack(ItemBuilder.get(command.args[2])); diff --git a/src/me/mehboss/commands/CommandRecipe.java b/src/me/mehboss/commands/CommandRecipe.java index 40135c5..fbf69a1 100644 --- a/src/me/mehboss/commands/CommandRecipe.java +++ b/src/me/mehboss/commands/CommandRecipe.java @@ -8,11 +8,15 @@ import me.mehboss.utils.RecipeUtil; import me.mehboss.utils.RecipeUtil.Recipe; import me.mehboss.utils.RecipeUtil.Recipe.RecipeType; +import me.mehboss.utils.data.BrewingRecipeData; +import me.mehboss.utils.data.CookingRecipeData; +import me.mehboss.utils.data.CraftingRecipeData; +import me.mehboss.utils.data.WorkstationRecipeData; public class CommandRecipe { static RecipeUtil getRecipeUtil() { - return Main.getInstance().recipeUtil; + return Main.getInstance().recipeUtil; } // Keep the method header as requested @@ -32,11 +36,11 @@ public static boolean Run(CRCommand command) { String typeRaw = args[1]; RecipeType type; try { - type = RecipeType.valueOf(typeRaw.toUpperCase()); + type = RecipeType.fromString(typeRaw.toUpperCase()); } catch (IllegalArgumentException ex) { sender.sendMessage(ChatColor.RED + "[CustomRecipes] Invalid recipe type: " + typeRaw); sender.sendMessage(ChatColor.GRAY - + "Valid types: SHAPELESS, SHAPED, STONECUTTER, FURNACE, ANVIL, BLASTFURNACE, SMOKER, CAMPFIRE, GRINDSTONE, BREWING_STAND"); + + "Valid types: SHAPELESS, SHAPED, STONECUTTER, FURNACE, ANVIL, BLASTFURNACE, SMOKER, CAMPFIRE, GRINDSTONE, BREWING"); return true; } @@ -61,11 +65,43 @@ public static boolean Run(CRCommand command) { // Optional permission String permission = (args.length >= 4) ? args[3] : "none"; + Recipe recipe; + switch (type) { + + case SHAPED: + case SHAPELESS: + recipe = new CraftingRecipeData(id); + break; + + case FURNACE: + case BLASTFURNACE: + case SMOKER: + case CAMPFIRE: + recipe = new CookingRecipeData(id); + break; + + case STONECUTTER: + case GRINDSTONE: + case ANVIL: + recipe = new WorkstationRecipeData(id); + break; + + case BREWING_STAND: + recipe = new BrewingRecipeData(id); + break; + + default: + throw new IllegalArgumentException("Unsupported recipe type: " + type); + } + + recipe.setType(type); + recipe.setPerm(permission); + sender.sendMessage( ChatColor.translateAlternateColorCodes('&', "&c[CustomRecipes] &fPreparing to create recipe:&r ")); sender.sendMessage(ChatColor.translateAlternateColorCodes('&', "&fType= &a" + type.toString() + " &fID= &a" + id + " &fPermission= &a" + permission)); - Main.getInstance().recipes.showCreationMenu(null, null, (Player) sender, id, permission, true, false, type); + Main.getInstance().recipes.showCreationMenu((Player) sender, recipe, true, false); return true; } } \ No newline at end of file diff --git a/src/me/mehboss/commands/CommandRecipes.java b/src/me/mehboss/commands/CommandRecipes.java new file mode 100644 index 0000000..5aa092a --- /dev/null +++ b/src/me/mehboss/commands/CommandRecipes.java @@ -0,0 +1,35 @@ +package me.mehboss.commands; + +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.entity.Player; + +import me.mehboss.recipe.Main; + +public class CommandRecipes implements CommandExecutor { + + FileConfiguration getConfig() { + return Main.getInstance().getConfig(); + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + final CRCommand cmd = new CRCommand(sender, command, label, args); + + if (!(sender instanceof Player)) { + sender.sendMessage("You must be a player in order to use this command!"); + return true; + } + + if (!sender.hasPermission("crecipe.book")) { + sender.sendMessage( + ChatColor.translateAlternateColorCodes('&', getConfig().getString("Messages.Invalid-Perms"))); + return true; + } + + return CommandBook.Run(cmd); + } +} diff --git a/src/me/mehboss/commands/CommandRemove.java b/src/me/mehboss/commands/CommandRemove.java new file mode 100644 index 0000000..0c5143d --- /dev/null +++ b/src/me/mehboss/commands/CommandRemove.java @@ -0,0 +1,49 @@ +package me.mehboss.commands; + +import java.io.File; + +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import me.mehboss.recipe.Main; +import me.mehboss.utils.RecipeUtil; +import me.mehboss.utils.RecipeUtil.Recipe; + +public class CommandRemove { + + static RecipeUtil getRecipeUtil() { + return Main.getInstance().recipeUtil; + } + + public static boolean Run(CRCommand command) { + CommandSender sender = command.sender; + String[] args = command.args; + + if (args.length < 2) { + sender.sendMessage(ChatColor.RED + "[CustomRecipes] Usage: /crecipe remove "); + return true; + } + + String id = args[1].toLowerCase(); + Recipe existing = getRecipeUtil().getRecipeFromKey(id); + if (existing == null) { + sender.sendMessage(ChatColor.RED + "[CustomRecipes] A recipe with the ID '" + id + "' could not be found."); + return true; + } + + File recipeFile = new File(Main.getInstance().getDataFolder(), "recipes/" + existing.getName() + ".yml"); + if (recipeFile.exists()) { + if (recipeFile.delete()) { + removeRecipe(existing.getName()); + sender.sendMessage(ChatColor.GREEN + "[CustomRecipes] Recipe '" + id + "' was successfully deleted."); + } else { + sender.sendMessage(ChatColor.RED + "[CustomRecipes] Failed to delete recipe '" + id + "'."); + } + } + return true; + } + + public static void removeRecipe(String name) { + getRecipeUtil().removeRecipe(name); + } + +} \ No newline at end of file diff --git a/src/me/mehboss/commands/CommandShow.java b/src/me/mehboss/commands/CommandShow.java index 81e1c1a..6f1f1d1 100644 --- a/src/me/mehboss/commands/CommandShow.java +++ b/src/me/mehboss/commands/CommandShow.java @@ -11,7 +11,7 @@ public class CommandShow { static RecipeUtil getRecipeUtil() { - return Main.getInstance().recipeUtil; + return Main.getInstance().recipeUtil; } public static boolean Run(CRCommand command) { @@ -22,7 +22,7 @@ public static boolean Run(CRCommand command) { sender.sendMessage(ChatColor.RED + "[CustomRecipes] Usage: /crecipe show "); return true; } - + String id = args[1].toLowerCase(); // Check if recipe already exists Recipe existing = getRecipeUtil().getRecipeFromKey(id); @@ -33,8 +33,7 @@ public static boolean Run(CRCommand command) { Player p = (Player) sender; sender.sendMessage(ChatColor.RED + "[CustomRecipes] Showing recipe with ID '" + id + "'.."); - Main.getInstance().recipes.showCreationMenu(null, existing.getResult(), p, existing.getName(), null, false, - false, null); + Main.getInstance().recipes.showCreationMenu(p, existing, false, false); return true; } } \ No newline at end of file diff --git a/src/me/mehboss/commands/TabCompletion.java b/src/me/mehboss/commands/TabCompletion.java index fe8f347..00b825a 100644 --- a/src/me/mehboss/commands/TabCompletion.java +++ b/src/me/mehboss/commands/TabCompletion.java @@ -2,7 +2,9 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.HashSet; import java.util.List; +import java.util.Set; import org.bukkit.Bukkit; import org.bukkit.command.Command; @@ -12,6 +14,8 @@ import org.bukkit.entity.Player; import org.bukkit.util.StringUtil; +import me.mehboss.utils.ItemBuilder; + import me.mehboss.recipe.Main; public class TabCompletion implements TabCompleter { @@ -30,6 +34,8 @@ public List onTabComplete(CommandSender sender, Command command, String } if (sender.hasPermission("crecipe.list")) subCommands.add("list"); + if (sender.hasPermission("crecipe.items")) + subCommands.add("items"); if (sender.hasPermission("crecipe.give")) subCommands.add("give"); if (sender.hasPermission("crecipe.reload")) @@ -57,7 +63,7 @@ public List onTabComplete(CommandSender sender, Command command, String } else if (args[0].equalsIgnoreCase("give") && args.length == 3 && sender.hasPermission("crecipe.give")) { // /customrecipes give - StringUtil.copyPartialMatches(args[2], getRecipes(), completions); + StringUtil.copyPartialMatches(args[2], getAllKeys(), completions); } else if (args[0].equalsIgnoreCase("edititem") && sender.hasPermission("crecipe.edititem")) { // /customrecipes edititem ... @@ -149,6 +155,7 @@ private List getRecipeTypes() { types.add("CAMPFIRE"); types.add("ANVIL"); types.add("GRINDSTONE"); + types.add("BREWING"); return types; } @@ -160,7 +167,14 @@ private List getOnlinePlayerNames() { return playerNames; } + private Collection getAllKeys() { + Set combined = new HashSet<>(); + combined.addAll(ItemBuilder.getAllItems()); + combined.addAll(Main.getInstance().recipeUtil.getAllKeys()); + return combined; + } + private Collection getRecipes() { - return Main.getInstance().recipeUtil.getAllRecipes().keySet(); + return Main.getInstance().recipeUtil.getAllKeys(); } } diff --git a/src/me/mehboss/commands/package-info.java b/src/me/mehboss/commands/package-info.java new file mode 100644 index 0000000..9e481b2 --- /dev/null +++ b/src/me/mehboss/commands/package-info.java @@ -0,0 +1,8 @@ +/** + * Provides all command handlers for the plugin, including administrative + * commands, player utilities, debug tools, and recipe management commands. + *

    + * Each command class implements a specific action such as listing recipes, + * reloading configuration, or giving custom items. + */ +package me.mehboss.commands; \ No newline at end of file diff --git a/src/me/mehboss/cooking/CookingManager.java b/src/me/mehboss/cooking/CookingManager.java index e3cec59..1a858f3 100644 --- a/src/me/mehboss/cooking/CookingManager.java +++ b/src/me/mehboss/cooking/CookingManager.java @@ -24,15 +24,39 @@ import me.mehboss.utils.RecipeUtil; import me.mehboss.utils.RecipeUtil.Recipe; +/** + * Handles custom furnace, smoker, and blast furnace recipe behaviors. + *

    + * Responsible for: + *

      + *
    • Tracking which player is interacting with which furnace
    • + *
    • Validating permissions before smelting custom results
    • + *
    • Preventing invalid or unauthorized smelting attempts
    • + *
    • Forcing furnace recipe refreshes to enable custom results
    • + *
    + * + * This system relies on {@link RecipeUtil} to determine whether a smelted output + * corresponds to a custom recipe, and applies appropriate permission/world checks. + */ public class CookingManager implements Listener { public HashMap cooking = new HashMap(); - /* - * Furnace start event - */ + /** + * Fired when a custom item is being smelted and the server is about to + * output a result item. + *

    + * This event performs: + *

      + *
    • Permission validation
    • + *
    • Disabled-world checks
    • + *
    • Cancellation of illegal smelts
    • + *
    + * + * @param e The smelt event containing the result and furnace context. + */ @EventHandler - public void onFurnaceSmelt(FurnaceSmeltEvent e) { + void onFurnaceSmelt(FurnaceSmeltEvent e) { ItemStack item = e.getResult(); Furnace f = (Furnace) e.getBlock().getState(); FurnaceInventory inv = f.getInventory(); @@ -64,8 +88,20 @@ public void onFurnaceSmelt(FurnaceSmeltEvent e) { logDebug("[FurnaceSmelt] Attempt to smelt " + recipe.getName() + " has been detected, handling override.."); } + /** + * Ensures correct result-matching for custom furnace recipes. + *

    + * When a furnace starts smelting: + *

      + *
    • Detects if a custom recipe should apply
    • + *
    • Prevents vanilla smelting from overriding custom smelts
    • + *
    • Forces a recipe refresh by inserting a dummy stone item
    • + *
    + * + * @param e FurnaceStartSmeltEvent triggered when the furnace begins smelting. + */ @EventHandler - public void onStart(FurnaceStartSmeltEvent e) { + void onStart(FurnaceStartSmeltEvent e) { if (Main.getInstance().serverVersionLessThan(1, 16)) return; @@ -91,8 +127,15 @@ public void onStart(FurnaceStartSmeltEvent e) { furnace.update(true, true); } + /** + * Tracks which player last interacted with a furnace/smoker/blast furnace. + *

    + * This allows permission checking when custom smelting begins. + * + * @param e InventoryClickEvent fired when a player clicks an inventory. + */ @EventHandler - public void onFurnaceClick(InventoryClickEvent e) { + void onFurnaceClick(InventoryClickEvent e) { if (e.getInventory().getType() == null) return; @@ -107,8 +150,14 @@ public void onFurnaceClick(InventoryClickEvent e) { } } + + /** + * Removes furnace tracking when the player closes the inventory. + * + * @param e InventoryCloseEvent triggered when inventory is closed. + */ @EventHandler - public void onFurnaceClose(InventoryCloseEvent e) { + void onFurnaceClose(InventoryCloseEvent e) { if (cooking.containsKey(e.getInventory())) cooking.remove(e.getInventory()); } diff --git a/src/me/mehboss/cooking/package-info.java b/src/me/mehboss/cooking/package-info.java new file mode 100644 index 0000000..b725286 --- /dev/null +++ b/src/me/mehboss/cooking/package-info.java @@ -0,0 +1,7 @@ +/** + * Contains custom furnace, smoker, and blast furnace processing logic. + *

    + * Handles custom smelting recipes, permission checks, furnace interaction + * tracking, and result overrides for cooking-based workstations. + */ +package me.mehboss.cooking; \ No newline at end of file diff --git a/src/me/mehboss/crafting/AmountManager.java b/src/me/mehboss/crafting/AmountManager.java index c464cf9..fde803d 100644 --- a/src/me/mehboss/crafting/AmountManager.java +++ b/src/me/mehboss/crafting/AmountManager.java @@ -4,17 +4,23 @@ import java.util.HashMap; import java.util.List; import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.entity.Player; +import org.bukkit.event.Event.Result; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; +import org.bukkit.event.inventory.ClickType; import org.bukkit.event.inventory.CraftItemEvent; import org.bukkit.event.inventory.InventoryAction; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryType.SlotType; +import org.bukkit.event.inventory.PrepareItemCraftEvent; import org.bukkit.inventory.CraftingInventory; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ShapelessRecipe; @@ -23,10 +29,13 @@ import io.github.bananapuncher714.nbteditor.NBTEditor; import me.mehboss.recipe.Main; +import me.mehboss.utils.CooldownManager.Cooldown; +import me.mehboss.utils.CooldownManager; import me.mehboss.utils.RecipeUtil; import me.mehboss.utils.RecipeUtil.Ingredient; import me.mehboss.utils.RecipeUtil.Recipe; import me.mehboss.utils.RecipeUtil.Recipe.RecipeType; +import me.mehboss.utils.data.CraftingRecipeData; public class AmountManager implements Listener { @@ -41,10 +50,18 @@ void logDebug(String st) { Logger.getLogger("Minecraft").log(Level.WARNING, "[DEBUG][" + Main.getInstance().getName() + "]" + st); } + CooldownManager getCooldownManager() { + return Main.getInstance().cooldownManager; + } + RecipeUtil getRecipeUtil() { return Main.getInstance().recipeUtil; } + ShapedChecks shapedChecks() { + return Main.getInstance().shapedChecks; + } + private boolean hasAllIngredients(CraftingInventory inv, String recipes, List recipeIngredients, UUID id) { return craftManager.hasAllIngredients(inv, recipes, recipeIngredients, id); @@ -56,8 +73,9 @@ private boolean hasMatchingDisplayName(String recipeName, ItemStack item, String } // Helper method for the handleShiftClick method - void handlesItemRemoval(CraftItemEvent e, CraftingInventory inv, Recipe recipe, ItemStack item, - RecipeUtil.Ingredient ingredient, int slot, int itemsToRemove, int itemsToAdd, int requiredAmount) { + void handlesItemRemoval(CraftItemEvent e, CraftingInventory inv, CraftingRecipeData recipe, ItemStack item, + RecipeUtil.Ingredient ingredient, int slot, int itemsToRemove, int itemsToAdd, int requiredAmount, + AtomicBoolean containerCraft) { String recipeName = recipe.getName(); logDebug("[handleShiftClicks] Checking slot " + slot + " for the recipe " + recipeName); @@ -90,12 +108,36 @@ void handlesItemRemoval(CraftItemEvent e, CraftingInventory inv, Recipe recipe, } if (e.getAction() != InventoryAction.MOVE_TO_OTHER_INVENTORY) { - if ((item.getAmount() + 1) - requiredAmount == 0) - inv.setItem(slot, null); - else + if (item.getAmount() == 1 && !containerCraft.get()) + return; + + if (((item.getAmount()) - requiredAmount == 0)) { + if (containerCraft.get() && !recipe.isLeftover(id)) { + Bukkit.getScheduler().runTask(Main.getInstance(), () -> { + inv.setItem(slot, null); + }); + return; + } + // set to 1, not 0, since we don't cancel the event. Vanilla will handle the + // leftover. + item.setAmount(1); + } else { + if (containerCraft.get()) { + // remove exact, since containers are cancelled, normal vanilla deduction does + // not happen. + item.setAmount((item.getAmount()) - requiredAmount); + return; + } item.setAmount((item.getAmount() + 1) - requiredAmount); + } } else { if ((item.getAmount() - itemsToRemove) <= 0) { + if (containerCraft.get() && !recipe.isLeftover(id)) { + Bukkit.getScheduler().runTask(Main.getInstance(), () -> { + inv.setItem(slot, null); + }); + return; + } inv.setItem(slot, null); return; } @@ -112,11 +154,10 @@ boolean matchesIngredient(ItemStack item, String recipeName, RecipeUtil.Ingredie ItemStack customItem = ingredient.hasIdentifier() ? getRecipeUtil().getResultFromKey(ingredient.getIdentifier()) : null; - return (customItem != null && item.isSimilar(customItem)) - || (craftManager.tagsMatch(ingredient, item) - || (ingredient.hasIdentifier() && exactMatch != null && item.isSimilar(exactMatch.getResult())) - || (item.getType() == material && hasMatchingDisplayName(recipeName, item, displayName, - ingredient.getIdentifier(), hasIdentifier, false))); + return (customItem != null && item.isSimilar(customItem)) || (craftManager.tagsMatch(ingredient, item) + || (ingredient.hasIdentifier() && exactMatch != null && item.isSimilar(exactMatch.getResult())) + || (item.getType() == material && hasMatchingDisplayName(recipeName, item, displayName, + ingredient.getIdentifier(), hasIdentifier, false))); } boolean isCraftingRecipe(RecipeType type) { @@ -133,222 +174,261 @@ void handleShiftClicks(CraftItemEvent e) { if (inv.getResult() == null || inv.getResult().getType() == Material.AIR) return; - // added debounce mechanism, which will automatically remove after 75ms to - // greatly improve server performance on large amounts of crafts (such as - // concrete power) + // debounce + logDebug("[handleShiftClicks] Fired #1"); + + Recipe matched = getRecipeUtil().getRecipeFromResult(inv.getResult()); Main.getInstance().debounceMap.put(id, System.currentTimeMillis()); - if (getRecipeUtil().getRecipeFromResult(inv.getResult()) == null) + if (matched == null) return; - logDebug("[handleShiftClicks] Fired amount checking mechanics.."); + logDebug("[handleShiftClicks] Fired #2"); if (e.getCurrentItem() == null || e.getCurrentItem().getType() == Material.AIR) return; - String findName = getRecipeUtil().getRecipeFromResult(inv.getResult()) != null - ? getRecipeUtil().getRecipeFromResult(inv.getResult()).getName() - : null; + ItemStack cursor = e.getCursor(); + ItemStack result = inv.getResult(); + String findName = matched.getName(); + boolean isShapeless = e.getRecipe() instanceof ShapelessRecipe; - final ItemStack result = inv.getResult(); - boolean isShapeless = e.getRecipe() instanceof ShapelessRecipe ? true : false; HashMap types = isShapeless ? getRecipeUtil().getRecipesFromType(RecipeType.SHAPELESS) : getRecipeUtil().getRecipesFromType(RecipeType.SHAPED); - logDebug("[handleShiftClicks] Initial recipe found recipe '" + findName + "' to handle.."); - if (NBTEditor.contains(inv.getResult(), NBTEditor.CUSTOM_DATA, "CUSTOM_ITEM_IDENTIFIER")) { String foundID = NBTEditor.getString(inv.getResult(), NBTEditor.CUSTOM_DATA, "CUSTOM_ITEM_IDENTIFIER"); - - if (getRecipeUtil().getRecipeFromKey(foundID) != null) { - findName = getRecipeUtil().getRecipeFromKey(foundID).getName(); - } + Recipe keyedRecipe = getRecipeUtil().getRecipeFromKey(foundID); + if (keyedRecipe != null) + findName = keyedRecipe.getName(); } - // safe guard check to prevent matches to other recipes types - if (types != null && !types.isEmpty()) - for (Recipe recipe : types.values()) { - ItemStack item = recipe.getResult(); - if (hasAllIngredients(inv, recipe.getName(), recipe.getIngredients(), id) - && (result.equals(item) || result.isSimilar(item))) { - findName = recipe.getName(); + // safeguard: match correct custom recipe result + if (types != null) + for (Recipe r : types.values()) { + ItemStack itm = r.getResult(); + if ((result.equals(itm) || result.isSimilar(itm)) + && hasAllIngredients(inv, r.getName(), r.getIngredients(), id)) { + findName = r.getName(); break; } } - logDebug("[handleShiftClicks] Actual found recipe '" + findName + "' to handle.."); + logDebug("[handleShiftClicks] Final recipe = " + findName); - if (findName == null) + CraftingRecipeData recipe = (CraftingRecipeData) getRecipeUtil().getRecipe(findName); + if (recipe == null) return; - logDebug("[handleShiftClicks] Paired it to a custom recipe. Running crafting amount calculations.."); + if (!recipe.isGrantItem()) { + if (e.getAction() == InventoryAction.DROP_ONE_SLOT || e.getAction() == InventoryAction.DROP_ALL_SLOT) { + logDebug("[handleShiftClicks] Denying craft due to drop request on an ungrantable item.."); + e.setCancelled(true); + return; + } + } - final String recipeName = findName; - if (e.isCancelled()) { - logDebug("Couldn't complete craftItemEvent for recipe " + recipeName - + ", the event was unexpectedly cancelled."); - logDebug("Please seek support or open a ticket https://github.com/mehboss/CustomRecipes/issues"); - return; + // prevents "ghost crafts" where the item isn't supposed to craft, but + // CraftItemEvent still fires. + if (cursor != null && cursor.getType() != Material.AIR) { + boolean canStackWithResult = cursor.isSimilar(result) + && cursor.getAmount() + result.getAmount() <= cursor.getMaxStackSize(); + + if (!canStackWithResult) { + e.setCancelled(true); + return; + } } - Recipe recipe = getRecipeUtil().getRecipe(recipeName); - ArrayList handledIngredients = new ArrayList(); + RecipeType type = recipe.getType(); + ArrayList handledIngredients = new ArrayList<>(); - // ========================= - // PASS 1: compute itemsToAdd - // bottleneck across ingredients based on required amounts - // ========================= + // ============================================================ + // PASS 1 — ITEMS TO ADD + // ============================================================ int itemsToAdd = Integer.MAX_VALUE; int itemsToRemove = 0; - for (RecipeUtil.Ingredient ingredient : recipe.getIngredients()) { - if (ingredient.isEmpty()) - continue; + ShapedChecks.AlignedResult aligned = null; + + if (type == RecipeType.SHAPED) { + + aligned = shapedChecks().getAlignedGrid(inv, recipe.getIngredients()); + if (aligned == null) { + // cannot craft at all + itemsToAdd = 0; + } else { + + for (int i = 0; i < recipe.getIngredients().size(); i++) { + + Ingredient ing = recipe.getIngredients().get(i); + if (ing.isEmpty()) + continue; + + ItemStack stack = aligned.getMatrix[i]; + if (stack == null || stack.getType() == Material.AIR) { + itemsToAdd = 0; + break; + } - final int requiredAmount = Math.max(1, ingredient.getAmount()); - int totalAvailable = 0; + int requiredAmount = Math.max(1, ing.getAmount()); + int possible = stack.getAmount() / requiredAmount; + itemsToAdd = Math.min(itemsToAdd, possible); + } + } + + } else { + // shapeless + for (Ingredient ing : recipe.getIngredients()) { + if (ing.isEmpty()) + continue; + int req = Math.max(1, ing.getAmount()); - if (recipe.getType() == RecipeType.SHAPELESS) { - // Sum across ALL crafting grid slots (1..9) that match THIS ingredient for (int i = 1; i < 10; i++) { ItemStack slot = inv.getItem(i); if (slot == null || slot.getType() == Material.AIR) continue; - if (matchesIngredient(slot, recipeName, ingredient, ingredient.getMaterial(), - ingredient.getDisplayName(), ingredient.hasIdentifier())) { - totalAvailable += slot.getAmount(); - } - } - } else { - // SHAPED / fixed-slot types: only the ingredient's slot counts - int fixed = ingredient.getSlot(); - ItemStack slot = inv.getItem(fixed); - if (slot != null && slot.getType() != Material.AIR && matchesIngredient(slot, recipeName, ingredient, - ingredient.getMaterial(), ingredient.getDisplayName(), ingredient.hasIdentifier())) { - totalAvailable = slot.getAmount(); + if (!matchesIngredient(slot, findName, ing, ing.getMaterial(), ing.getDisplayName(), + ing.hasIdentifier())) + continue; + + int possible = slot.getAmount() / req; + itemsToAdd = Math.min(itemsToAdd, possible); } } - - int possibleForThisIngredient = totalAvailable / requiredAmount; // floor - itemsToAdd = Math.min(itemsToAdd, possibleForThisIngredient); } - // If nothing craftable, bail before removal logic if (itemsToAdd <= 0 || itemsToAdd == Integer.MAX_VALUE) { - logDebug("[handleShiftClicks][" + findName - + "] An issue has been detected whilest calculating amount deductions.."); - logDebug("Please reach out for support to report this.. this shouldn't happen."); + logDebug("[handleShiftClicks][" + findName + "] No possible crafts."); e.setCancelled(true); return; } - // ========================= - // PASS 2: - // ========================= - for (RecipeUtil.Ingredient ingredient : recipe.getIngredients()) { - if (ingredient.isEmpty()) - continue; + boolean recipeHasContainer = recipe.getAllIngredientTypes().stream() + .anyMatch(mat -> mat == XMaterial.DRAGON_BREATH.get() || mat == XMaterial.POTION.get() + || mat == XMaterial.LINGERING_POTION.get() || mat.toString().contains("_BUCKET")); + AtomicBoolean containerCraft = new AtomicBoolean(recipeHasContainer); - final Material material = ingredient.getMaterial(); - final String displayName = ingredient.getDisplayName(); - final int requiredAmount = Math.max(1, ingredient.getAmount()); - final boolean hasIdentifier = ingredient.hasIdentifier(); + // ============================================================ + // PASS 2 — REMOVE ITEMS + // ============================================================ + if (type == RecipeType.SHAPED) { - if (recipe.getType() == RecipeType.SHAPELESS) { - logDebug("[handleShiftClicks] Found shapeless recipe to handle.."); + for (int i = 0; i < recipe.getIngredients().size(); i++) { - for (int i = 1; i < 10; i++) { - ItemStack item = inv.getItem(i); - int slot = i; + Ingredient ing = recipe.getIngredients().get(i); + if (ing.isEmpty()) + continue; - if (item == null || item.getType() == Material.AIR) - continue; + int realInvSlot = aligned.invSlotMap[i]; + ItemStack stack = inv.getItem(realInvSlot); + if (stack == null || stack.getType() == Material.AIR) + continue; - if (!matchesIngredient(item, recipeName, ingredient, material, displayName, hasIdentifier)) - continue; + int requiredAmount = Math.max(1, ing.getAmount()); - if (!handledIngredients.contains(ingredient.getAbbreviation())) { - handlesItemRemoval(e, inv, recipe, item, ingredient, slot, itemsToRemove, itemsToAdd, - requiredAmount); - } - } + handlesItemRemoval(e, inv, recipe, stack, ing, realInvSlot, itemsToRemove, itemsToAdd, requiredAmount, + containerCraft); + } - if (!handledIngredients.contains(ingredient.getAbbreviation())) - handledIngredients.add(ingredient.getAbbreviation()); + } else { + // shapeless + for (Ingredient ing : recipe.getIngredients()) { + if (ing.isEmpty()) + continue; - } else { - logDebug("[handleShiftClicks] Found other recipe type to handle.."); + final Material mat = ing.getMaterial(); + final String name = ing.getDisplayName(); + final boolean hasID = ing.hasIdentifier(); + int req = Math.max(1, ing.getAmount()); - ItemStack item = inv.getItem(ingredient.getSlot()); - int slot = ingredient.getSlot(); + for (int i = 1; i < 10; i++) { + ItemStack stack = inv.getItem(i); + if (stack == null || stack.getType() == Material.AIR) + continue; - if (item == null || item.getType() == Material.AIR) - continue; + if (!matchesIngredient(stack, findName, ing, mat, name, hasID)) + continue; - handlesItemRemoval(e, inv, recipe, item, ingredient, slot, itemsToRemove, itemsToAdd, requiredAmount); + if (!handledIngredients.contains(ing.getAbbreviation())) { + handlesItemRemoval(e, inv, recipe, stack, ing, i, itemsToRemove, itemsToAdd, req, + containerCraft); + } + } + + handledIngredients.add(ing.getAbbreviation()); } } + // ============================================================ + // COMMANDS + OUTPUT + // ============================================================ Player player = (Player) e.getWhoClicked(); - Main.getInstance().cooldownManager.setCooldown(player.getUniqueId(), recipe.getKey(), - System.currentTimeMillis()); + Cooldown cooldown = new Cooldown(recipe.getKey(), recipe.getCooldown()); + getCooldownManager().addCooldown(player.getUniqueId(), cooldown); - // Delayed task to prevent debug spam + // debug suppression window Main.getInstance().inInventory.add(id); - Bukkit.getScheduler().runTaskLater(Main.getInstance(), () -> { - Main.getInstance().inInventory.remove(id); - }, 2L); + Bukkit.getScheduler().runTaskLater(Main.getInstance(), () -> Main.getInstance().inInventory.remove(id), 2L); - // Handles the commands portion if (recipe.hasCommands()) { - logDebug("[ExecuteCMD] Found commands to run for recipe " + recipeName); - if (e.getAction() != InventoryAction.MOVE_TO_OTHER_INVENTORY) itemsToAdd = 1; - for (int n = 0; n < itemsToAdd; n++) { - for (String command : recipe.getCommand()) { - String parsed = command.replace("%crafter%", player.getName()); - Bukkit.dispatchCommand(Bukkit.getConsoleSender(), parsed); - } - } + for (int n = 0; n < itemsToAdd; n++) + for (String cmd : recipe.getCommand()) + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), cmd.replace("%crafter%", player.getName())); if (!recipe.isGrantItem()) { - logDebug("[ExecuteCMD] Cancelling craft of " + recipeName - + " because a command to perform was found instead."); - Bukkit.getScheduler().runTaskLater(Main.getInstance(), new Runnable() { - @Override - public void run() { - e.getWhoClicked().setItemOnCursor(null); - } - }, 2L); - + Bukkit.getScheduler().runTaskLater(Main.getInstance(), () -> e.getWhoClicked().setItemOnCursor(null), + 2L); return; } } - // Add the result items to the player's inventory if (e.getAction() != InventoryAction.MOVE_TO_OTHER_INVENTORY) { - logDebug("[handleShiftClicks] Didn't detect shift click from inventory.. Ignoring.."); - - } else { - e.setCancelled(true); + logDebug("[handleShiftClicks] Normal click, no mass-add"); - if (recipe.hasCommands() && !recipe.isGrantItem()) - return; + if (containerCraft.get()) { + e.setCancelled(true); - inv.setResult(new ItemStack(Material.AIR)); + logDebug("[handleShiftClicks] Manual handle of container, since vanilla mechanics replace."); + if (cursor == null || cursor.getType() == Material.AIR) + player.setItemOnCursor(result.clone()); + else if (cursor.isSimilar(result)) + cursor.setAmount(cursor.getAmount() + result.getAmount()); + else + return; - for (int i = 0; i < itemsToAdd; i++) { - if (player.getInventory().firstEmpty() == -1) { - player.getLocation().getWorld().dropItem(player.getLocation(), result); - continue; - } - player.getInventory().addItem(result); + // since we cancel, we have to retrigger an evaluation manually + Bukkit.getScheduler().runTask(Main.getInstance(), () -> { + PrepareItemCraftEvent event = new PrepareItemCraftEvent(inv, e.getView(), false); + Bukkit.getPluginManager().callEvent(event); + }); } - logDebug("[handleShiftClicks] Shift click detected. Adding " + itemsToAdd + " to inventory."); - logDebug("[handleShiftClicks] Added " + itemsToAdd + " items and removed items from table."); + if (itemsToAdd == 1) + Bukkit.getScheduler().runTask(Main.getInstance(), () -> { + inv.setResult(new ItemStack(Material.AIR)); + }); + return; + } + + // SHIFT CLICK OUTPUT + e.setCancelled(true); + + if (recipe.hasCommands() && !recipe.isGrantItem()) + return; + + inv.setResult(new ItemStack(Material.AIR)); + + for (int i = 0; i < itemsToAdd; i++) { + if (player.getInventory().firstEmpty() == -1) + player.getWorld().dropItem(player.getLocation(), result.clone()); + else + player.getInventory().addItem(result.clone()); } + + logDebug("[handleShiftClicks] Added x" + itemsToAdd); } } diff --git a/src/me/mehboss/crafting/CooldownManager.java b/src/me/mehboss/crafting/CooldownManager.java deleted file mode 100644 index 1b1ae74..0000000 --- a/src/me/mehboss/crafting/CooldownManager.java +++ /dev/null @@ -1,91 +0,0 @@ -package me.mehboss.crafting; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; - -import me.mehboss.recipe.Main; - -public class CooldownManager { - private Map> playerCooldowns = new HashMap<>(); // Keyed by player UUID, then by recipe ID - - // Sets the cooldown for a player and a specific recipe by recipeID - public void setCooldown(UUID player, String recipeID, long time) { - if (time < 1) { - removeCooldown(player, recipeID); // Remove cooldown if time is less than 1 - } else { - playerCooldowns - .computeIfAbsent(player, k -> new HashMap<>()) // Ensure the player's cooldown map exists - .put(recipeID, time); // Set the cooldown for the specific recipe - } - } - - // Removes the cooldown for a specific recipe for a player by recipeID - public void removeCooldown(UUID player, String recipeID) { - if (playerCooldowns.containsKey(player)) { - playerCooldowns.get(player).remove(recipeID); - } - } - - // Get the time left for the recipe cooldown by recipeID - public Long getTimeLeft(UUID player, String recipeID) { - if (recipeID == null || !hasCooldown(player, recipeID)) { - return 0L; - } - - long timeLeft = getCooldownTime(recipeID) - - TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - - playerCooldowns.getOrDefault(player, new HashMap<>()).getOrDefault(recipeID, 0L)); - - return timeLeft; - } - - // Checks if the cooldown for a recipe by recipeID has expired for the player - public boolean cooldownExpired(UUID player, String recipeID) { - if (recipeID == null || !hasCooldown(player, recipeID)) { - return true; // If recipe doesn't have a cooldown, treat it as expired - } - - long timeLeft = TimeUnit.MILLISECONDS - .toSeconds(System.currentTimeMillis() - - playerCooldowns.getOrDefault(player, new HashMap<>()).getOrDefault(recipeID, 0L)); - - // Assuming getCooldownTime() returns the cooldown period for a given recipeID - if (timeLeft >= getCooldownTime(recipeID)) { - removeCooldown(player, recipeID); // Remove the expired cooldown - return true; - } - - return false; - } - - // Returns true if the player has a cooldown for the recipe by recipeID - public boolean hasCooldown(UUID player, String recipeID) { - return playerCooldowns.containsKey(player) && playerCooldowns.get(player).containsKey(recipeID); - } - - // Assuming you have a method to get cooldown time for a given recipeID (you can customize this) - private long getCooldownTime(String recipeID) { - // Placeholder: You would need to replace this with actual logic to fetch the cooldown for a given recipe - // This could be from a configuration, a database, etc. - return Main.getInstance().recipeUtil.getRecipeFromKey(recipeID).getCooldown(); // Example: Default cooldown time for all recipes is 60 seconds - } - - // Returns the player's cooldown map for debugging or other purposes - public Map> getCooldowns() { - return playerCooldowns; - } - - // Sets the entire cooldown map, in case you need to load it from a file/database - public void setCooldownMap(Map> map) { - try { - playerCooldowns = map; - } catch (Exception e) { - Main.getInstance().getLogger().log(Level.SEVERE, - "An error has occurred while attempting to fetch recipe cooldowns"); - e.printStackTrace(); - } - } -} \ No newline at end of file diff --git a/src/me/mehboss/crafting/CraftManager.java b/src/me/mehboss/crafting/CraftManager.java index 3a4d378..9d0c94b 100644 --- a/src/me/mehboss/crafting/CraftManager.java +++ b/src/me/mehboss/crafting/CraftManager.java @@ -15,17 +15,17 @@ */ import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; import org.bukkit.Bukkit; +import org.bukkit.ChatColor; import org.bukkit.Material; -import org.bukkit.attribute.Attribute; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -36,25 +36,26 @@ import org.bukkit.inventory.CraftingInventory; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.ShapelessRecipe; import org.bukkit.inventory.meta.ItemMeta; import com.cryptomorin.xseries.XMaterial; -import com.ssomar.score.api.executableitems.ExecutableItemsAPI; -import com.ssomar.score.api.executableitems.config.ExecutableItemInterface; - -import dev.lone.itemsadder.api.CustomStack; import io.github.bananapuncher714.nbteditor.NBTEditor; import me.clip.placeholderapi.PlaceholderAPI; +import me.mehboss.crafting.ShapedChecks.AlignedResult; import me.mehboss.recipe.Main; import me.mehboss.utils.CompatibilityUtil; +import me.mehboss.utils.CooldownManager; import me.mehboss.utils.RecipeConditions.ConditionSet; import me.mehboss.utils.RecipeUtil; import me.mehboss.utils.RecipeUtil.Recipe; import me.mehboss.utils.RecipeUtil.Recipe.RecipeType; -import net.md_5.bungee.api.ChatColor; +import me.mehboss.utils.data.CraftingRecipeData; public class CraftManager implements Listener { + FileConfiguration customConfig() { + return Main.getInstance().customConfig; + } + ArrayList disabledrecipe() { return Main.getInstance().disabledrecipe; } @@ -63,14 +64,22 @@ Logger getLogger() { return Main.getInstance().getLogger(); } - FileConfiguration customConfig() { - return Main.getInstance().customConfig; + CooldownManager getCooldownManager() { + return Main.getInstance().cooldownManager; } RecipeUtil getRecipeUtil() { return Main.getInstance().recipeUtil; } + ShapedChecks shapedChecks() { + return Main.getInstance().shapedChecks; + } + + ShapelessChecks shapelessChecks() { + return Main.getInstance().shapelessChecks; + } + boolean isInt(String s) { try { Integer.parseInt(s); @@ -103,7 +112,6 @@ void sendNoPermsMessage(Player p, String recipe) { } public boolean matchedRecipe(CraftingInventory inv, UUID id) { -// if (result == null || result == new ItemStack(Material.AIR)) { if (Main.getInstance().serverVersionAtLeast(1, 13)) { if (inv.isEmpty()) { logDebug("[matchedRecipe] Could not find a recipe to match with!", "", id); @@ -127,25 +135,44 @@ public boolean matchedRecipe(CraftingInventory inv, UUID id) { boolean validateItem(ItemStack item, RecipeUtil.Ingredient ingredient, String recipeName, int slot, boolean debug, boolean returnType) { - ItemStack recipe = getRecipeUtil().getRecipeFromResult(item) != null - ? getRecipeUtil().getRecipeFromResult(item).getResult() - : null; - ItemStack exactMatch = ingredient.hasIdentifier() ? getRecipeUtil().getResultFromKey(ingredient.getIdentifier()) - : recipe; + if (item == null) + return returnType; + + ItemStack recipeResult = null; + Recipe foundRecipe = getRecipeUtil().getRecipeFromResult(item); + if (foundRecipe != null) { + recipeResult = foundRecipe.getResult(); + } - if (item == null || (ingredient.hasIdentifier() && exactMatch == null)) + ItemStack exactMatch = null; + boolean matchesIdentifier = false; + boolean matchesTags = tagsMatch(ingredient, item); + + // since getResultFromKey is so resource heavy, try to check tags first for + // custom items. + if (!matchesTags) { + exactMatch = ingredient.hasIdentifier() ? getRecipeUtil().getResultFromKey(ingredient.getIdentifier()) + : recipeResult; + matchesIdentifier = ingredient.hasIdentifier() && item.isSimilar(exactMatch); + } + + if (ingredient.hasIdentifier() && !matchesTags && exactMatch == null) return returnType; - // Attempt to match the ingredient to a recipe using the identifier - // Attempt to match the ingredient to an itemsadder item - // If all else fail, match the itemstack to the regular ingredient requirements - if ((ingredient.hasIdentifier() && item.isSimilar(exactMatch)) || (tagsMatch(ingredient, exactMatch)) - || (item.getType() == ingredient.getMaterial() && hasMatchingDisplayName(recipeName, item, - ingredient.getDisplayName(), ingredient.getIdentifier(), ingredient.hasIdentifier(), false))) { + boolean matchesMaterial = item.getType() == ingredient.getMaterial(); + boolean matchesMaterialChoice = ingredient.hasMaterialChoices() + && ingredient.getMaterialChoices().contains(item.getType()); + boolean matchesSingleMaterialWithName = matchesMaterial && hasMatchingDisplayName(recipeName, item, + ingredient.getDisplayName(), ingredient.getIdentifier(), ingredient.hasIdentifier(), false); + + if (matchesIdentifier || matchesTags || matchesMaterialChoice || matchesSingleMaterialWithName) { if (debug) logDebug("[amountsMatch] Checking slot " + slot + " for required amounts.. ", recipeName); + if (ingredient.isEmpty()) + return true; + if (item.getAmount() < ingredient.getAmount()) { if (debug) { @@ -161,45 +188,66 @@ boolean validateItem(ItemStack item, RecipeUtil.Ingredient ingredient, String re return true; } + return returnType; } - boolean amountsMatch(CraftingInventory inv, String recipeName, List recipeIngredients, - boolean debug, UUID id) { + boolean amountsMatch(AlignedResult alignedGrid, Inventory inv, String recipeName, + List recipeIngredients, boolean debug, UUID id) { logDebug("[amountsMatch] Checking recipe amounts..", recipeName, id); + + boolean isCraftingInventory = inv.getType() == InventoryType.WORKBENCH + || inv.getType() == InventoryType.CRAFTING; + RecipeType type = getRecipeUtil().getRecipe(recipeName).getType(); + ItemStack[] matrix = inv.getContents(); + + boolean[] usedSlots = new boolean[matrix.length]; for (RecipeUtil.Ingredient ingredient : recipeIngredients) { - if (ingredient.isEmpty()) - continue; - int slot = ingredient.getSlot(); - if (getRecipeUtil().getRecipe(recipeName).getType() == RecipeType.SHAPED) { - ItemStack invSlot = inv.getMatrix()[slot - 1]; + if (type == RecipeType.SHAPED) { + + if (alignedGrid == null) + return false; + + int slot = ingredient.getSlot(); + ItemStack invSlot = alignedGrid.getMatrix[slot - 1]; + + if (ingredient.isEmpty()) { + continue; + } if (!validateItem(invSlot, ingredient, recipeName, slot, debug, false)) return false; - } - if (getRecipeUtil().getRecipe(recipeName).getType() == RecipeType.SHAPELESS) { - slot = 0; - for (ItemStack item : inv.getMatrix()) { + continue; + + } else { + if (ingredient.isEmpty()) + continue; + int slot = isCraftingInventory ? 0 : -1; + + for (ItemStack item : matrix) { + if (isCraftingInventory && slot == 0) { + slot++; + continue; + } + + if (usedSlots[slot]) + continue; + if (item == null || item.getType() == Material.AIR) + continue; + if (validateItem(item, ingredient, recipeName, slot, debug, false)) { + usedSlots[slot] = true; + break; + } slot++; - if (!validateItem(item, ingredient, recipeName, slot, debug, true)) - return false; } } } return true; } - boolean hasCooldown(Player p, Recipe recipe) { - if (recipe.hasCooldown()) - if (!(Main.getInstance().cooldownManager.cooldownExpired(p.getUniqueId(), recipe.getKey()))) - return true; - - return false; - } - @SuppressWarnings("deprecation") boolean isBlacklisted(ItemStack result, Player p) { if (customConfig().getBoolean("blacklist-recipes")) { @@ -288,92 +336,6 @@ public boolean hasMatchingDisplayName(String recipeName, ItemStack item, String } } - // Checks if inventory slots and recipe materials match - public boolean hasShapedIngredients(Inventory inv, String recipeName, - List recipeIngredients) { - ArrayList invMaterials = new ArrayList<>(); - - boolean isCraftingInventory = inv.getType() == InventoryType.WORKBENCH - || inv.getType() == InventoryType.CRAFTING; - int slot = 0; - for (ItemStack contents : inv.getContents()) { - if (isCraftingInventory && slot == 0) { - slot++; - continue; - } - if (invMaterials.size() == 9) - break; - - if (contents == null || contents.getType() == Material.AIR) { - invMaterials.add("null"); - continue; - } - - invMaterials.add(contents.getType().toString()); - } - - slot = 0; - for (RecipeUtil.Ingredient ingredient : recipeIngredients) { - - if (ingredient.isEmpty() && !invMaterials.get(slot).equals("null")) { - logDebug("[hasShapedIngredients] Required ingredient not found in slot " + slot, recipeName); - return false; - } - - if (!ingredient.isEmpty()) { - if (ingredient.hasIdentifier()) { - String ingMat = getRecipeUtil().getResultFromKey(ingredient.getIdentifier()).getType().toString(); - if (!invMaterials.get(slot).equals(ingMat)) { - logDebug("[hasShapedIngredients] Recipe ingredient requirement for slot " + slot - + " does not match the ID ingredient", recipeName); - logDebug("[hasShapedIngredients] Required ingredient: " + ingMat + " - Found: " - + invMaterials.get(slot), recipeName); - return false; - } - slot++; - continue; - } - - if (!invMaterials.get(slot).equals(ingredient.getMaterial().toString())) { - logDebug("[hasShapedIngredients] Recipe ingredient requirement for slot " + slot - + " does not match the ingredient", recipeName); - logDebug("[hasShapedIngredients] Required ingredient: " + ingredient.getMaterial().toString() - + " - Found: " + invMaterials.get(slot), recipeName); - return false; - } - } - - slot++; - } - - if (invMaterials.size() != 9) { - logDebug( - "[hasShapedIngredients] An internal error has occurred.. Please contact mehboss on spigot! Found size " - + invMaterials.size() + "but should be 9", - recipeName); - - return false; - } - return true; - } - - // Method for specifically shapeless recipes only (checks all inv materials) - public ArrayList getIngredients(String recipeName, - List allIngredients) { - ArrayList newIngredients = new ArrayList<>(); - - for (RecipeUtil.Ingredient ingredient : allIngredients) { - // Ignore the slot field and only save material, amount, and slot - if (!ingredient.isEmpty() && ingredient.getMaterial() != null) { - if (!newIngredients.contains(ingredient)) { - newIngredients.add(ingredient); - } - } - } - - return newIngredients; - } - // Checks all inv materials for recipe (shaped or shapeless) public boolean hasAllIngredients(Inventory inv, String recipeName, List recipeIngredients, UUID id) { @@ -383,6 +345,12 @@ public boolean hasAllIngredients(Inventory inv, String recipeName, List leftOvers = new ArrayList<>(); for (RecipeUtil.Ingredient ingredient : recipeIngredients) { if (!ingredient.isEmpty()) { @@ -408,8 +377,20 @@ public boolean hasAllIngredients(Inventory inv, String recipeName, List types = Arrays.asList("WORKBENCH", "CRAFTER"); + if (types.contains(inv.getType().toString())) { - return false; + if (invMaterials.size() != 9 || ingMaterials.size() != 9) { + logDebug("[hasAllIngredients] Ingredients size is " + ingMaterials.size() + ", Inventory size is " + + invMaterials.size(), recipeName, id); + return false; + } + + Map invCount = new HashMap<>(); + Map ingCount = new HashMap<>(); + + for (String s : invMaterials) + invCount.put(s, invCount.getOrDefault(s, 0) + 1); + + for (String s : ingMaterials) + ingCount.put(s, ingCount.getOrDefault(s, 0) + 1); + + for (Map.Entry e : ingCount.entrySet()) { + if (invCount.getOrDefault(e.getKey(), 0) < e.getValue()) { + logDebug("[hasAllIngredients] Missing required: " + e.getKey(), recipeName, id); + return false; + } + } } - // handles 4x4 slot - if (inv.getType() == InventoryType.CRAFTING - && (!invMaterials.containsAll(ingMaterials) || !ingMaterials.containsAll(invMaterials))) { - logDebug("[hasAllIngredients] Recipe ingredient requirements not met..", recipeName, id); - return false; + // handles 2x2 slot + if (inv.getType() == InventoryType.CRAFTING) { + Map invCount = new HashMap<>(); + Map ingCount = new HashMap<>(); + + for (String s : invMaterials) + invCount.put(s, invCount.getOrDefault(s, 0) + 1); + + for (String s : ingMaterials) + ingCount.put(s, ingCount.getOrDefault(s, 0) + 1); + + for (Map.Entry e : ingCount.entrySet()) { + if (invCount.getOrDefault(e.getKey(), 0) < e.getValue()) { + logDebug("[hasAllIngredients] Recipe ingredient requirements not met..", recipeName, id); + return false; + } + } } return true; @@ -463,398 +475,29 @@ public boolean hasVanillaIngredients(Inventory inv, ItemStack result) { if (item == null || item.getType() == Material.AIR) continue; - if (item.hasItemMeta() && (item.getItemMeta().hasDisplayName() || item.getItemMeta().hasLore())) + if (item.hasItemMeta() + && (CompatibilityUtil.hasDisplayname(item.getItemMeta()) || item.getItemMeta().hasLore())) return false; } + logDebug("[hasVanillaIngredients] Skipping checks.. vanilla recipe detected.", ""); return true; } boolean tagsMatch(RecipeUtil.Ingredient ingredient, ItemStack item) { - String invID = getRecipeUtil().getKeyFromResult(item); + String key = getRecipeUtil().getKeyFromResult(item); + String invID = key != null ? key.toLowerCase() : "none"; String ingID = ingredient.hasIdentifier() ? ingredient.getIdentifier().toLowerCase() : "none"; - logDebug("[tagsMatch] invID === " + invID, ""); - logDebug("[tagsMatch] ingID === " + ingID, ""); - if (invID == null && ingID.equals("none")) + logDebug("[tagsMatch] Inventory Tag: " + invID, ""); + logDebug("[tagsMatch] Ingredient Tag: " + ingID, ""); + if (invID.equals("none") && ingID.equals("none")) return true; - if (invID != null && invID.toLowerCase().equals(ingID)) + if (!invID.equals("none") && invID.equals(ingID)) return true; return false; } -// Will be removed in a later version, after testing. -// -// -// boolean checkCustomItems(ItemStack exactMatch, ItemStack item, String identifier, Boolean logDebug) { -// ItemStack ingredientItem = exactMatch != null ? exactMatch.clone() : null; -// ItemMeta ingredientMeta = ingredientItem != null ? ingredientItem.getItemMeta() : null; -// ItemStack inventoryItem = item.clone(); -// ItemMeta inventoryMeta = item.getItemMeta(); -// -// String customItem = getRecipeUtil().getCustomItemPlugin(identifier); -// if (customItem != null -// && (customItem.toLowerCase().equals("mythicmobs") || customItem.toLowerCase().equals("itemsadder"))) { -// for (Attribute am : Attribute.values()) { -// inventoryMeta.removeAttributeModifier(am); -// if (ingredientItem != null) -// ingredientMeta.removeAttributeModifier(am); -// } -// -// ingredientItem.setItemMeta(ingredientMeta); -// inventoryItem.setItemMeta(inventoryMeta); -// } -// -// if (ingredientItem == null || inventoryItem == null) -// return false; -// if (ingredientItem.isSimilar(inventoryItem)) -// return true; -// -// if (customItem != null) { -// if (customItem.toLowerCase().equals("itemsadder")) { -// if (!Main.getInstance().hasCustomPlugin("itemsadder")) -// return false; -// -// CustomStack stack = CustomStack.byItemStack(inventoryItem); -// if (stack == null) { -// logDebug("[checkCustomItems] ItemsAdder stack came back null, by error.", "", null); -// logDebug("Item in question: " + identifier, ""); -// return false; -// } -// -// String iaID = identifier.split(":")[1].toLowerCase(); -// String invID = stack.getNamespacedID().split(":")[1].toLowerCase(); -// -// if (invID.equals(iaID)) -// return true; -// } -// -// if (customItem.toLowerCase().equals("executableitems")) { -// if (Main.getInstance().hasCustomPlugin("executableitems")) -// return false; -// -// Optional ei = ExecutableItemsAPI.getExecutableItemsManager() -// .getExecutableItem(inventoryItem); -// if (!ei.isPresent()) { -// logDebug("[checkCustomItems] EI stack came back null, by error.", ""); -// logDebug("Item in question: " + identifier, ""); -// return false; -// } -// -// String iaID = identifier.split(":")[1].toLowerCase(); -// String invID = ei.get().getId().toLowerCase(); -// -// if (invID.equals(iaID)) -// return true; -// } -// } -// -// return false; -// } - - boolean handleShapelessRecipe(CraftingInventory inv, Recipe recipe, List recipeIngredients, - UUID id) { - logDebug("[handleShapeless] Handling shapeless checks..!", recipe.getName(), id); - - ArrayList slotNames = new ArrayList<>(); - ArrayList recipeNames = new ArrayList<>(); - - ArrayList recipeMD = new ArrayList<>(); - ArrayList inventoryMD = new ArrayList<>(); - - ArrayList slotIDs = new ArrayList<>(); - ArrayList recipeIDs = new ArrayList<>(); - - Map recipeCount = new HashMap<>(); - Map inventoryCount = new HashMap<>(); - - // Add 4 empties to the 4x4 slot to makeup for the missing slots - if (inv.getType() == InventoryType.CRAFTING) { - inventoryCount.put(Material.AIR, 5); - Collections.addAll(slotNames, "null", "null", "null", "null", "null"); - Collections.addAll(slotIDs, "null", "null", "null", "null", "null"); - Collections.addAll(inventoryMD, -1, -1, -1, -1, -1); - - } - - for (RecipeUtil.Ingredient ingredient : recipeIngredients) { - if (ingredient.isEmpty()) { - recipeCount.put(Material.AIR, recipeCount.getOrDefault(Material.AIR, 0) + 1); - recipeNames.add("null"); - continue; - } - - if (ingredient.hasIdentifier() && getRecipeUtil().getResultFromKey(ingredient.getIdentifier()) != null) { - ItemStack exactMatch = getRecipeUtil().getResultFromKey(ingredient.getIdentifier()); - recipeCount.put(exactMatch.getType(), recipeCount.getOrDefault(exactMatch.getType(), 0) + 1); - recipeIDs.add(ingredient.getIdentifier()); - - // recipe model data - if (Main.getInstance().serverVersionAtLeast(1, 14)) { - if (exactMatch.hasItemMeta() && exactMatch.getItemMeta().hasCustomModelData()) { - recipeMD.add(exactMatch.getItemMeta().getCustomModelData()); - } else { - recipeMD.add(-1); - } - } - - // recipe names - if (exactMatch.getItemMeta().hasDisplayName()) { - recipeNames.add(exactMatch.getItemMeta().getDisplayName()); - } else { - recipeNames.add("false"); - } - continue; - } - - recipeCount.put(ingredient.getMaterial(), recipeCount.getOrDefault(ingredient.getMaterial(), 0) + 1); - recipeIDs.add("null"); - - if (Main.getInstance().serverVersionAtLeast(1, 14)) - recipeMD.add(ingredient.getCustomModelData()); - - if (ingredient.hasDisplayName()) { - recipeNames.add(ingredient.getDisplayName()); - } else { - recipeNames.add("false"); - } - } - - ItemStack[] matrix = inv.getMatrix(); // len 4 or 9 - - for (int i = 0; i < matrix.length; i++) { - ItemStack it = matrix[i]; - - if (it == null || it.getType() == Material.AIR || it.getAmount() <= 0) { - slotNames.add("null"); - inventoryCount.put(Material.AIR, inventoryCount.getOrDefault(Material.AIR, 0) + 1); - continue; - } - - // check ids - String key = getRecipeUtil().getKeyFromResult(it); - if (key == null) - key = "null"; - slotIDs.add(key); - - // inv model data - if (Main.getInstance().serverVersionAtLeast(1, 14)) { - if (it.hasItemMeta() && it.getItemMeta().hasCustomModelData()) { - inventoryMD.add(it.getItemMeta().getCustomModelData()); - } else { - inventoryMD.add(-1); - } - } - - inventoryCount.put(it.getType(), inventoryCount.getOrDefault(it.getType(), 0) + 1); - - if (!it.hasItemMeta() || !it.getItemMeta().hasDisplayName()) { - slotNames.add("false"); - continue; - } - - slotNames.add(it.getItemMeta().getDisplayName()); - } - - boolean hasIngredients = true; - for (Map.Entry entry : recipeCount.entrySet()) { - Material material = entry.getKey(); - int requiredCount = entry.getValue(); - int invCount = inventoryCount.getOrDefault(material, 0); - - if (invCount < requiredCount) { - hasIngredients = false; - break; - } - } - - if (!hasIngredients) { - logDebug("[handleShapeless] Missing required ingredients! Skipping recipe..", recipe.getName(), id); - return false; - } - logDebug("[handleShapeless] All required ingredients found..", recipe.getName(), id); - - // checks hardcoded nbt tags - if (!recipeIDs.equals(slotIDs) || !slotIDs.equals(recipeIDs)) { - logDebug("[handleShapeless] NBT tags mismatch: recipe vs inventory", recipe.getName(), id); - return false; - } - - // ignore data - if (recipe.getIgnoreData()) - return true; - - // ignore model data - if (!recipe.getIgnoreModelData()) { - Map recipeModelCount = new HashMap<>(); - Map inventoryModelCount = new HashMap<>(); - - for (int model : recipeMD) { - recipeModelCount.put(model, recipeModelCount.getOrDefault(model, 0) + 1); - } - for (int model : inventoryMD) { - inventoryModelCount.put(model, inventoryModelCount.getOrDefault(model, 0) + 1); - } - - if (!recipeMD.containsAll(inventoryMD) || !inventoryMD.containsAll(recipeMD)) { - logDebug("[handleShapeless] Model data mismatch: recipe vs inventory", recipe.getName(), id); - return false; - } - - if (!recipeModelCount.equals(inventoryModelCount)) { - logDebug("[handleShapeless] Model data mismatch: recipe vs inventory", recipe.getName(), id); - logDebug(" RM: " + recipeModelCount, recipe.getName(), id); - logDebug(" IM: " + inventoryModelCount, recipe.getName(), id); - return false; - } - } - - // ignore names - if (!recipe.getIgnoreNames()) { - Map recipeNameCount = new HashMap<>(); - Map slotNameCount = new HashMap<>(); - - for (String name : recipeNames) { - recipeNameCount.put(name, recipeNameCount.getOrDefault(name, 0) + 1); - } - for (String name : slotNames) { - slotNameCount.put(name, slotNameCount.getOrDefault(name, 0) + 1); - } - - if (!recipeNames.containsAll(slotNames) || !slotNames.containsAll(recipeNames)) { - logDebug("[handleShapeless] Display name mismatch: recipe vs inventory", recipe.getName(), id); - logDebug(slotNames.toString(), recipe.getName(), id); - logDebug(recipeNames.toString(), recipe.getName(), id); - return false; - } - - if (!recipeNameCount.equals(slotNameCount)) { - logDebug("[handleShapeless] Display name count mismatch: recipe vs inventory", recipe.getName(), id); - logDebug(" RN: " + recipeNameCount, recipe.getName(), id); - logDebug(" IN: " + slotNameCount, recipe.getName(), id); - return false; - } - } - return true; - } - - boolean handleShapedRecipe(CraftingInventory inv, Recipe recipe, List recipeIngredients) { - int i = 0; - - for (RecipeUtil.Ingredient ingredient : recipeIngredients) { - i++; - - if (inv.getItem(i) != null && inv.getItem(i).getType() != Material.AIR) { - ItemMeta meta = inv.getItem(i).getItemMeta(); - - // checks hard coded nbt tags - if (ingredient.hasIdentifier()) { - String invID = getRecipeUtil().getKeyFromResult(inv.getItem(i)); - - if (invID == null || !invID.equals(ingredient.getIdentifier())) { - logDebug("[handleShaped] Skipping recipe..", recipe.getName()); - logDebug("[handleShaped] IDs do not match", recipe.getName()); - logDebug("[handleShaped] IngID = ", ingredient.getIdentifier()); - logDebug("[handleShaped] InvID = ", invID); - return false; - } - - logDebug("[handleShaped] Passed all checks for the ingredient in slot " + i, recipe.getName()); - continue; - - } - - logDebug("[handleShaped] Ingredient slot " + i + " does not have an identifier..", recipe.getName()); - if (recipe.getIgnoreData()) { - logDebug("[handleShaped] Skipping data checks.. Ignore-Data is enabled..", recipe.getName()); - continue; - } - - // only if ignore names is false - if (!recipe.getIgnoreNames()) { - // checks if displayname is null - if ((!meta.hasDisplayName() && ingredient.hasDisplayName()) - || (meta.hasDisplayName() && !ingredient.hasDisplayName())) { - logDebug("[handleShaped] Skipping recipe..", recipe.getName()); - logDebug( - "[handleShaped] The recipe ingredient displayname and the inventory slot displayname do not match", - recipe.getName()); - logDebug( - "[handleShaped] The inventory slot in question: " + i - + ". The ingredient slot in question: " + ingredient.getSlot(), - recipe.getName()); - logDebug( - "[handleShaped] Does the ingredient have a displayname? " + ingredient.hasDisplayName(), - recipe.getName()); - logDebug("[handleShaped] Does the inventory have a displayname? " + meta.hasDisplayName(), - recipe.getName()); - return false; - } - - if (ingredient.hasDisplayName() && meta.hasDisplayName() - && !(ingredient.getDisplayName().equals(meta.getDisplayName()))) { - logDebug("[handleShaped] Skipping recipe..", recipe.getName()); - logDebug("[handleShaped] The ingredient name for the recipe and inventory do not match", - recipe.getName()); - logDebug( - "[handleShaped] The inventory slot in question: " + i - + ". The ingredient slot in question: " + ingredient.getSlot(), - recipe.getName()); - logDebug("[handleShaped] The ingredient displayname: " + ingredient.getDisplayName(), - recipe.getName()); - logDebug("[handleShaped] The inventory displayname: " + meta.getDisplayName(), - recipe.getName()); - return false; - } - logDebug("[handleShaped] Inventory and recipe ingredient displayname matched for slot " + i, - recipe.getName()); - } - - if (!Main.getInstance().serverVersionAtLeast(1, 14)) { - logDebug("[handleShaped] Skipping CMD checks.. Version is less than 1.14..", recipe.getName()); - continue; - } - - // only if ignore model data is false - if (!recipe.getIgnoreModelData()) { - if ((!meta.hasCustomModelData() && ingredient.hasCustomModelData()) - || (meta.hasCustomModelData() && !ingredient.hasCustomModelData())) { - logDebug("[handleShaped] Skipping recipe..", recipe.getName()); - logDebug("[handleShaped] The recipe ingredient CMD and the inventory slot CMD do not match..", - recipe.getName()); - logDebug( - "[handleShaped] The inventory slot in question: " + i - + ". The ingredient slot in question: " + ingredient.getSlot(), - recipe.getName()); - logDebug("[handleShaped] Does the ingredient have CMD? " + ingredient.hasCustomModelData(), - recipe.getName()); - logDebug("[handleShaped] Does the inventory have CMD? " + meta.hasCustomModelData(), - recipe.getName()); - return false; - } - - if (ingredient.hasCustomModelData() && meta.hasCustomModelData() - && (ingredient.getCustomModelData() != meta.getCustomModelData())) { - logDebug("[handleShaped] Skipping recipe..", recipe.getName()); - logDebug("[handleShaped] The ingredient CMD for the recipe and inventory do not match..", - recipe.getName()); - logDebug( - "[handleShaped] The inventory slot in question: " + i - + ". The ingredient slot in question: " + ingredient.getSlot(), - recipe.getName()); - logDebug("[handleShaped] The ingredient CMD: " + ingredient.getCustomModelData(), - recipe.getName()); - logDebug("[handleShaped] The inventory CMD: " + meta.getCustomModelData(), recipe.getName()); - return false; - } - logDebug("[handleShaped] Inventory and recipe ingredient CMD matched for slot " + i, - recipe.getName()); - } - } - } - return true; - } - @EventHandler(priority = EventPriority.LOWEST) void handleCrafting(PrepareItemCraftEvent e) { @@ -889,33 +532,34 @@ void handleCrafting(PrepareItemCraftEvent e) { if (hasVanillaIngredients(inv, inv.getResult())) return; - if (inv.getType() == InventoryType.CRAFTING) { - if (!(inv.getRecipe() instanceof ShapelessRecipe)) { - if (getRecipeUtil().getRecipeFromResult(inv.getResult()) != null) - inv.setResult(new ItemStack(Material.AIR)); - return; - } - } +// if (inv.getType() == InventoryType.CRAFTING) { +// if (!(inv.getRecipe() instanceof ShapelessRecipe)) { +// logDebug("[handleCrafting] Skipping 2x2 checks.. recipe isn't shapeless..", ""); +// +// if (getRecipeUtil().getRecipeFromResult(inv.getResult()) != null) +// inv.setResult(new ItemStack(Material.AIR)); +// return; +// } +// } logDebug("[handleCrafting] Fired craft event, beginning checks..", "", p.getUniqueId()); handleCraftingChecks(inv, p); } public void handleCraftingChecks(CraftingInventory inv, Player p) { - Recipe finalRecipe = null; + CraftingRecipeData recipe = null; Boolean passedCheck = true; Boolean found = false; UUID id = p.getUniqueId(); + AlignedResult alignedGrid = null; - for (Recipe recipe : getRecipeUtil().getAllRecipesSortedByResult(inv.getResult())) { - finalRecipe = recipe; - - List recipeIngredients = recipe.getIngredients(); - - if (recipe.getType() != RecipeType.SHAPELESS && recipe.getType() != RecipeType.SHAPED) + for (Recipe data : getRecipeUtil().getAllRecipesSortedByResult(inv.getResult())) { + if (data.getType() != RecipeType.SHAPELESS && data.getType() != RecipeType.SHAPED) continue; + recipe = (CraftingRecipeData) data; + List recipeIngredients = recipe.getIngredients(); if (!hasAllIngredients(inv, recipe.getName(), recipeIngredients, id)) { logDebug("[handleCrafting] Skipping to the next recipe! Ingredients did not match..", recipe.getName(), id); @@ -924,79 +568,90 @@ public void handleCraftingChecks(CraftingInventory inv, Player p) { } logDebug("[handleCrafting] Inventory contained all of the ingredients. Continuing checks.. RecipeType: " - + recipe.getType(), recipe.getName()); + + recipe.getType(), recipe.getName(), id); passedCheck = true; found = true; + // ------------------------- + // SHAPELESS + // ------------------------- if (recipe.getType() == RecipeType.SHAPELESS) { - if (!handleShapelessRecipe(inv, recipe, recipeIngredients, id)) { + + if (!shapelessChecks().handleShapelessRecipe(inv, recipe, recipeIngredients, id)) { passedCheck = false; continue; } - } else { + } - if (!hasShapedIngredients(inv, recipe.getName(), recipeIngredients)) { + // ------------------------- + // SHAPED + // ------------------------- + else { + + alignedGrid = shapedChecks().getAlignedGrid(inv, recipeIngredients); + if (alignedGrid == null) { logDebug("[handleCrafting] Skipping to the next recipe! Ingredients did not have exact match..", - recipe.getName()); + recipe.getName(), id); passedCheck = false; continue; } - logDebug("[handleCrafting] Ingredients matched. Continuing checks..", recipe.getName()); - - if (recipe.getIgnoreData() == true) - continue; - - if (!handleShapedRecipe(inv, recipe, recipeIngredients)) { - passedCheck = false; - continue; + logDebug("[handleCrafting] Ingredients matched. Continuing checks..", recipe.getName(), id); - } + // strict checks (NBT / CMD / name) + if (!recipe.getIgnoreData()) + if (!shapedChecks().handleShapedRecipe(inv.getType(), alignedGrid, recipe, recipeIngredients)) { + passedCheck = false; + continue; + } } - if (!(amountsMatch(inv, recipe.getName(), recipeIngredients, true, id))) { + // ------------------------- + // AMOUNT CHECK + // ------------------------- + if (!amountsMatch(alignedGrid, inv, recipe.getName(), recipeIngredients, true, id)) { logDebug( "[handleCrafting] Skipping recipe.. The amount check indicates that the requirements have not been met..", - recipe.getName()); + recipe.getName(), id); passedCheck = false; continue; } - if (passedCheck && found) { + if (passedCheck && found) break; - - } } if (!found) return; logDebug("[handleCrafting] Final crafting results: (passedChecks: " + passedCheck + ")(foundRecipe: " + found - + ")", finalRecipe.getName(), id); + + ")", recipe.getName(), id); if ((!passedCheck) || (passedCheck && !found)) { inv.setResult(new ItemStack(Material.AIR)); return; } - // checks if recipe is active - if (!finalRecipe.isActive()) { + // ------------------------- + // DISABLED CHECKS + // ------------------------- + if (!recipe.isActive()) { inv.setResult(new ItemStack(Material.AIR)); - logDebug(" Attempt to craft disabled recipe detected..", finalRecipe.getName()); + logDebug(" Attempt to craft disabled recipe detected..", recipe.getName(), id); return; } - // checks for required perms - if (finalRecipe.hasPerm() && !p.hasPermission(finalRecipe.getPerm())) { + // PERMISSIONS + if (recipe.hasPerm() && !p.hasPermission(recipe.getPerm())) { inv.setResult(new ItemStack(Material.AIR)); - sendNoPermsMessage(p, finalRecipe.getName()); + sendNoPermsMessage(p, recipe.getName()); return; } - // checks for disabled world - if (!(finalRecipe.getDisabledWorlds().isEmpty())) { - for (String string : finalRecipe.getDisabledWorlds()) { + // WORLD DISABLED + if (!(recipe.getDisabledWorlds().isEmpty())) { + for (String string : recipe.getDisabledWorlds()) { if (p.getWorld().getName().equalsIgnoreCase(string)) { inv.setResult(new ItemStack(Material.AIR)); sendMessages(p, "none", 0); @@ -1005,21 +660,22 @@ public void handleCraftingChecks(CraftingInventory inv, Player p) { } } - // checks for cool down - if ((finalRecipe.hasPerm() && !(p.hasPermission(finalRecipe.getPerm() + ".bypass"))) - && hasCooldown(p, finalRecipe)) { - Long timeLeft = Main.getInstance().cooldownManager.getTimeLeft(p.getUniqueId(), finalRecipe.getKey()); + // COOLDOWN + if ((recipe.hasPerm() && !(p.hasPermission(recipe.getPerm() + ".bypass"))) + && getCooldownManager().hasCooldown(p.getUniqueId(), recipe.getKey())) { + Long timeLeft = Main.getInstance().cooldownManager.getTimeLeft(p.getUniqueId(), recipe.getKey()); sendMessages(p, "crafting-limit", timeLeft); inv.setResult(new ItemStack(Material.AIR)); return; } - // checks recipe conditions - ConditionSet cs = finalRecipe.getConditionSet(); + // CONDITIONS + ConditionSet cs = recipe.getConditionSet(); if (cs != null && !cs.isEmpty()) { List reasons = new ArrayList(); if (!cs.test(p.getLocation(), p, reasons)) { + if (!reasons.isEmpty()) { p.sendMessage(ChatColor.RED + "You cannot craft this recipe until conditions are met: " + ChatColor.GRAY + String.join(", ", reasons)); @@ -1027,20 +683,25 @@ && hasCooldown(p, finalRecipe)) { Boolean closeInventory = Main.getInstance().customConfig.getBoolean("conditions-failed.close-inventory", true); + if (closeInventory) p.closeInventory(); - logDebug(" Preventing craft due to failing required recipe conditions!", finalRecipe.getName()); + logDebug(" Preventing craft due to failing required recipe conditions!", recipe.getName(), id); inv.setResult(new ItemStack(Material.AIR)); return; } } + // ------------------------- + // SUCCESSFUL CRAFT + // ------------------------- if (passedCheck && found) { - ItemStack item = new ItemStack(finalRecipe.getResult()); + ItemStack item = new ItemStack(recipe.getResult()); + + // PlaceholderAPI support + if (!recipe.isCustomItem()) { - // only parse placeholders for items that are NOT IA, MM, ETC. - if (!finalRecipe.isCustomItem()) { List withPlaceholders = null; if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) @@ -1048,9 +709,8 @@ && hasCooldown(p, finalRecipe)) { ? PlaceholderAPI.setPlaceholders(p, item.getItemMeta().getLore()) : null; - ItemMeta itemMeta = item.getItemMeta(); - if (withPlaceholders != null) { + ItemMeta itemMeta = item.getItemMeta(); itemMeta.setLore(withPlaceholders); item.setItemMeta(itemMeta); } diff --git a/src/me/mehboss/crafting/CrafterManager.java b/src/me/mehboss/crafting/CrafterManager.java index 9a52605..48f5d32 100644 --- a/src/me/mehboss/crafting/CrafterManager.java +++ b/src/me/mehboss/crafting/CrafterManager.java @@ -1,54 +1,60 @@ package me.mehboss.crafting; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; +import org.bukkit.Location; import org.bukkit.Material; -import org.bukkit.block.Container; +import org.bukkit.block.Block; +import org.bukkit.block.Crafter; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.block.CrafterCraftEvent; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; import com.cryptomorin.xseries.XMaterial; + +import me.mehboss.crafting.ShapedChecks.AlignedResult; import me.mehboss.recipe.Main; import me.mehboss.utils.RecipeUtil; import me.mehboss.utils.RecipeConditions.ConditionSet; import me.mehboss.utils.RecipeUtil.Recipe; import me.mehboss.utils.RecipeUtil.Recipe.RecipeType; +import me.mehboss.utils.data.CraftingRecipeData; public class CrafterManager implements Listener { @EventHandler(priority = EventPriority.HIGHEST) public void onCollectResult(CrafterCraftEvent e) { - Container block = (Container) e.getBlock().getState(); - Inventory inv = block.getInventory(); - e.setResult(handleCraftingChecks(e, inv, e.getResult())); + Block block = e.getBlock(); + + if (!(block.getState() instanceof Crafter)) + return; + + Crafter crafter = (Crafter) block.getState(); + Inventory inv = crafter.getInventory(); + e.setResult(handleCraftingChecks(crafter, inv, e.getResult())); } - ItemStack handleCraftingChecks(CrafterCraftEvent e, Inventory inv, ItemStack result) { - Recipe finalRecipe = null; + ItemStack handleCraftingChecks(Crafter crafter, Inventory inv, ItemStack result) { + CraftingRecipeData recipe = null; Boolean passedCheck = true; Boolean found = false; + AlignedResult grid = null; if (CraftManager().hasVanillaIngredients(inv, result)) return result; - for (Recipe recipe : getRecipeUtil().getAllRecipesSortedByResult(result)) { - - finalRecipe = recipe; - - List recipeIngredients = recipe.getIngredients(); + for (Recipe data : getRecipeUtil().getAllRecipesSortedByResult(result)) { - if (recipe.getType() != RecipeType.SHAPELESS && recipe.getType() != RecipeType.SHAPED) + if (data.getType() != RecipeType.SHAPELESS && data.getType() != RecipeType.SHAPED) continue; + recipe = (CraftingRecipeData) data; + List recipeIngredients = recipe.getIngredients(); if (!CraftManager().hasAllIngredients(inv, recipe.getName(), recipeIngredients, null)) { logDebug("[handleCrafting] Skipping to the next recipe! Ingredients did not match..", recipe.getName()); passedCheck = false; @@ -61,14 +67,24 @@ ItemStack handleCraftingChecks(CrafterCraftEvent e, Inventory inv, ItemStack res passedCheck = true; found = true; + // ------------------------- + // SHAPELESS RECIPE CHECKS + // ------------------------- if (recipe.getType() == RecipeType.SHAPELESS) { - if (!handleShapelessRecipe(inv, result, recipe, recipeIngredients)) { + + if (!shapelessChecks().handleShapelessRecipe(inv, recipe, recipeIngredients, null)) { passedCheck = false; continue; } - } else { - if (!CraftManager().hasShapedIngredients(inv, recipe.getName(), recipeIngredients)) { + } + // ---------------------- + // SHAPED RECIPE CHECKS + // ---------------------- + else { + + grid = shapedChecks().getAlignedGrid(inv, recipeIngredients); + if (grid == null) { logDebug("[handleCrafting] Skipping to the next recipe! Ingredients did not have exact match..", recipe.getName()); passedCheck = false; @@ -77,17 +93,18 @@ ItemStack handleCraftingChecks(CrafterCraftEvent e, Inventory inv, ItemStack res logDebug("[handleCrafting] Ingredients matched. Continuing checks..", recipe.getName()); - if (recipe.getIgnoreData() == true) - continue; - - if (!handleShapedRecipe(inv, recipe, recipeIngredients)) { - passedCheck = false; - continue; - - } + // NEW: run strict NBT-name-CMD checks on aligned grid + if (!recipe.getIgnoreData()) + if (!shapedChecks().handleShapedRecipe(inv.getType(), grid, recipe, recipeIngredients)) { + passedCheck = false; + continue; + } } - if (!(amountsMatch(inv, recipe.getName(), recipeIngredients, true))) { + // ----------------------- + // AMOUNT CHECK + // ----------------------- + if (!(CraftManager().amountsMatch(grid, inv, recipe.getName(), recipeIngredients, true, null))) { logDebug( "[handleCrafting] Skipping recipe.. The amount check indicates that the requirements have not been met..", recipe.getName()); @@ -97,12 +114,11 @@ ItemStack handleCraftingChecks(CrafterCraftEvent e, Inventory inv, ItemStack res if (passedCheck && found) { break; - } } logDebug("[handleCrafting] Final crafting results: (passedChecks: " + passedCheck + ")(foundRecipe: " + found - + ")", finalRecipe.getName()); + + ")", recipe.getName()); if (!found) return result; @@ -111,455 +127,146 @@ ItemStack handleCraftingChecks(CrafterCraftEvent e, Inventory inv, ItemStack res return new ItemStack(Material.AIR); } - if (!finalRecipe.isActive() || finalRecipe.getDisabledWorlds().contains(e.getBlock().getWorld().getName()) - || Main.getInstance().disabledrecipe.contains(finalRecipe.getKey())) { - logDebug(" Attempt to craft recipe was detected, but recipe is disabled!", finalRecipe.getName()); + Location location = crafter.getBlock().getLocation(); + if (!recipe.isActive() || recipe.getDisabledWorlds().contains(location.getWorld().getName()) + || Main.getInstance().disabledrecipe.contains(recipe.getKey()) || recipe.hasCommands()) { + + logDebug(" Attempt to craft recipe was detected, but recipe is disabled!", recipe.getName()); return new ItemStack(Material.AIR); } - // checks recipe conditions - ConditionSet cs = finalRecipe.getConditionSet(); + // CONDITIONS + ConditionSet cs = recipe.getConditionSet(); if (cs != null && !cs.isEmpty()) { - if (!cs.test(e.getBlock().getLocation(), null, null)) { - logDebug("Preventing craft of due to failing required recipe conditions!", finalRecipe.getName()); + if (!cs.test(location, null, null)) { + logDebug("Preventing craft due to failing required recipe conditions!", recipe.getName()); return new ItemStack(Material.AIR); } } if (passedCheck && found) { - ItemStack item = new ItemStack(finalRecipe.getResult()); - handleAmountDeductions(e, finalRecipe.getName(), inv); + ItemStack item = new ItemStack(recipe.getResult()); + handleAmountDeductions(grid, crafter, recipe.getName(), inv); return item; } return result; } - boolean amountsMatch(Inventory inv, String recipeName, List recipeIngredients, - boolean debug) { - - logDebug("[amountsMatch] Checking recipe amounts..", recipeName); - for (RecipeUtil.Ingredient ingredient : recipeIngredients) { - if (ingredient.isEmpty()) - continue; - - int slot = ingredient.getSlot(); - if (getRecipeUtil().getRecipe(recipeName).getType() == RecipeType.SHAPED) { - ItemStack invSlot = inv.getContents()[slot - 1]; - - if (!CraftManager().validateItem(invSlot, ingredient, recipeName, slot, debug, false)) - return false; - } - - if (getRecipeUtil().getRecipe(recipeName).getType() == RecipeType.SHAPELESS) { - slot = 0; - for (ItemStack item : inv.getContents()) { - if (!CraftManager().validateItem(item, ingredient, recipeName, slot, debug, true)) - return false; - slot++; - } - } - } - return true; - } - - boolean handleShapelessRecipe(Inventory inv, ItemStack result, Recipe recipe, - List recipeIngredients) { - // runs checks if recipe is shapeless - - logDebug("[handleShapeless] Handling shapeless checks..!", recipe.getName()); - if (getRecipeUtil().getRecipeFromResult(result) != null) - recipe = getRecipeUtil().getRecipeFromResult(result); - - ArrayList slotNames = new ArrayList<>(); - ArrayList recipeNames = new ArrayList<>(); - - ArrayList recipeMD = new ArrayList<>(); - ArrayList inventoryMD = new ArrayList<>(); - - ArrayList slotIDs = new ArrayList<>(); - ArrayList recipeIDs = new ArrayList<>(); - - Map recipeCount = new HashMap<>(); - Map inventoryCount = new HashMap<>(); - - for (RecipeUtil.Ingredient ingredient : recipeIngredients) { - if (ingredient.isEmpty()) { - recipeCount.put(Material.AIR, recipeCount.getOrDefault(Material.AIR, 0) + 1); - recipeNames.add("null"); - continue; - } - - if (ingredient.hasIdentifier() && getRecipeUtil().getResultFromKey(ingredient.getIdentifier()) != null) { - ItemStack exactMatch = getRecipeUtil().getResultFromKey(ingredient.getIdentifier()); - recipeCount.put(exactMatch.getType(), recipeCount.getOrDefault(exactMatch.getType(), 0) + 1); - recipeIDs.add(ingredient.getIdentifier()); - - // recipe model data - if (Main.getInstance().serverVersionAtLeast(1, 14)) { - if (exactMatch.hasItemMeta() && exactMatch.getItemMeta().hasCustomModelData()) { - recipeMD.add(exactMatch.getItemMeta().getCustomModelData()); - } else { - recipeMD.add(-1); - } - } - - // recipe names - if (exactMatch.getItemMeta().hasDisplayName()) { - recipeNames.add(exactMatch.getItemMeta().getDisplayName()); - } else { - recipeNames.add("false"); - } - continue; - } - - recipeCount.put(ingredient.getMaterial(), recipeCount.getOrDefault(ingredient.getMaterial(), 0) + 1); - recipeIDs.add("null"); - - if (Main.getInstance().serverVersionAtLeast(1, 14)) - recipeMD.add(ingredient.getCustomModelData()); - - if (ingredient.hasDisplayName()) { - recipeNames.add(ingredient.getDisplayName()); - } else { - recipeNames.add("false"); - } - } - - for (int i = 0; i < 9; i++) { - ItemStack it = inv.getItem(i); - - if (it == null || it.getType() == Material.AIR || it.getAmount() <= 0) { - slotNames.add("null"); - inventoryCount.put(Material.AIR, inventoryCount.getOrDefault(Material.AIR, 0) + 1); - continue; - } - - // check ids - String key = getRecipeUtil().getKeyFromResult(it); - if (key == null) - key = "null"; - slotIDs.add(key); - - // inv model data - if (Main.getInstance().serverVersionAtLeast(1, 14)) { - if (it.hasItemMeta() && it.getItemMeta().hasCustomModelData()) { - inventoryMD.add(it.getItemMeta().getCustomModelData()); - } else { - inventoryMD.add(-1); - } - } - - inventoryCount.put(it.getType(), inventoryCount.getOrDefault(it.getType(), 0) + 1); - - if (!it.hasItemMeta() || !it.getItemMeta().hasDisplayName()) { - slotNames.add("false"); - continue; - } - - slotNames.add(it.getItemMeta().getDisplayName()); - } - - boolean hasIngredients = true; - for (Map.Entry entry : recipeCount.entrySet()) { - Material material = entry.getKey(); - int requiredCount = entry.getValue(); - int invCount = inventoryCount.getOrDefault(material, 0); - - if (invCount < requiredCount) { - hasIngredients = false; - break; - } - } - - if (!hasIngredients) { - logDebug("[handleShapeless] Missing required ingredients! Skipping recipe..", recipe.getName()); - return false; - } - - logDebug("[handleShapeless] All required ingredients found..", recipe.getName()); - // checks hardcoded nbt tags - if (!recipeIDs.equals(slotIDs) || !slotIDs.equals(recipeIDs)) { - logDebug("[handleShapeless] NBT tags mismatch: recipe vs inventory", recipe.getName()); - return false; - } - - // ignore data - if (recipe.getIgnoreData()) - return true; - - // ignore model data - if (!recipe.getIgnoreModelData()) { - Map recipeModelCount = new HashMap<>(); - Map inventoryModelCount = new HashMap<>(); - - for (int model : recipeMD) { - recipeModelCount.put(model, recipeModelCount.getOrDefault(model, 0) + 1); - } - for (int model : inventoryMD) { - inventoryModelCount.put(model, inventoryModelCount.getOrDefault(model, 0) + 1); - } - - if (!recipeMD.containsAll(inventoryMD) || !inventoryMD.containsAll(recipeMD)) { - logDebug("[handleShapeless] Model data mismatch: recipe vs inventory", recipe.getName()); - return false; - } - - if (!recipeModelCount.equals(inventoryModelCount)) { - logDebug("[handleShapeless] Model data mismatch: recipe vs inventory", recipe.getName()); - logDebug(" Recipe Model Data Map: " + recipeModelCount, recipe.getName()); - logDebug(" Inventory Model Data Map: " + inventoryModelCount, recipe.getName()); - return false; - } - } - - // ignore names - if (!recipe.getIgnoreNames()) { - Map recipeNameCount = new HashMap<>(); - Map slotNameCount = new HashMap<>(); - - for (String name : recipeNames) { - recipeNameCount.put(name, recipeNameCount.getOrDefault(name, 0) + 1); - } - for (String name : slotNames) { - slotNameCount.put(name, slotNameCount.getOrDefault(name, 0) + 1); - } - - if (!recipeNames.containsAll(slotNames) || !slotNames.containsAll(recipeNames)) { - logDebug("[handleShapeless] Display name mismatch: recipe vs inventory", recipe.getName()); - logDebug(slotNames.toString(), recipe.getName()); - logDebug(recipeNames.toString(), recipe.getName()); - return false; - } - - if (!recipeNameCount.equals(slotNameCount)) { - logDebug("[handleShapeless] Display name count mismatch: recipe vs inventory", recipe.getName()); - logDebug(" RN: " + recipeNameCount, recipe.getName()); - logDebug(" IN: " + slotNameCount, recipe.getName()); - return false; - } - } - return true; - } - - boolean handleShapedRecipe(Inventory inv, Recipe recipe, List recipeIngredients) { - int i = -1; + void handleAmountDeductions(AlignedResult aligned, Crafter crafter, String recipeName, Inventory inv) { + CraftingRecipeData recipe = (CraftingRecipeData) getRecipeUtil().getRecipe(recipeName); + if (recipe == null) + return; - for (RecipeUtil.Ingredient ingredient : recipeIngredients) { - i++; + ArrayList handledIngredients = new ArrayList<>(); + boolean[] handledSlots = new boolean[9]; - if (inv.getItem(i) != null && inv.getItem(i).getType() != Material.AIR) { - ItemMeta meta = inv.getItem(i).getItemMeta(); - - // checks hard coded nbt tags - if (ingredient.hasIdentifier()) { - String invID = getRecipeUtil().getKeyFromResult(inv.getItem(i)); - - if (invID == null || !invID.equals(ingredient.getIdentifier())) { - logDebug("[handleShaped] Skipping recipe..", recipe.getName()); - logDebug("[handleShaped] IDs do not match", recipe.getName()); - logDebug("[handleShaped] IngID = ", ingredient.getIdentifier()); - logDebug("[handleShaped] InvID = ", invID); - return false; - } + final int itemsToAdd = 1; + int itemsToRemove = 0; - logDebug("[handleShaped] Passed all checks for the ingredient in slot " + i, recipe.getName()); - continue; + RecipeType type = recipe.getType(); - } + if (type == RecipeType.SHAPED) { + if (aligned == null) + return; - logDebug("[handleShaped] Ingredient slot " + i + " does not have an identifier..", recipe.getName()); - if (recipe.getIgnoreData()) { - logDebug("[handleShaped] Skipping data checks.. Ignore-Data is enabled..", recipe.getName()); + for (int i = 0; i < recipe.getIngredients().size(); i++) { + RecipeUtil.Ingredient ing = recipe.getIngredients().get(i); + if (ing.isEmpty()) continue; - } - - // only if ignore names is false - if (!recipe.getIgnoreNames()) { - // checks if displayname is null - if ((!meta.hasDisplayName() && ingredient.hasDisplayName()) - || (meta.hasDisplayName() && !ingredient.hasDisplayName())) { - logDebug("[handleShaped] Skipping recipe..", recipe.getName()); - logDebug( - "[handleShaped] The recipe ingredient displayname and the inventory slot displayname do not match", - recipe.getName()); - logDebug( - "[handleShaped] The inventory slot in question: " + i - + ". The ingredient slot in question: " + ingredient.getSlot(), - recipe.getName()); - logDebug( - "[handleShaped] Does the ingredient have a displayname? " + ingredient.hasDisplayName(), - recipe.getName()); - logDebug("[handleShaped] Does the inventory have a displayname? " + meta.hasDisplayName(), - recipe.getName()); - return false; - } - if (ingredient.hasDisplayName() && meta.hasDisplayName() - && !(ingredient.getDisplayName().equals(meta.getDisplayName()))) { - logDebug("[handleShaped] Skipping recipe..", recipe.getName()); - logDebug("[handleShaped] The ingredient name for the recipe and inventory do not match", - recipe.getName()); - logDebug( - "[handleShaped] The inventory slot in question: " + i - + ". The ingredient slot in question: " + ingredient.getSlot(), - recipe.getName()); - logDebug("[handleShaped] The ingredient displayname: " + ingredient.getDisplayName(), - recipe.getName()); - logDebug("[handleShaped] The inventory displayname: " + meta.getDisplayName(), - recipe.getName()); - return false; - } - logDebug("[handleShaped] Inventory and recipe ingredient displayname matched for slot " + i, - recipe.getName()); - } - - if (!Main.getInstance().serverVersionAtLeast(1, 14)) { - logDebug("[handleShaped] Skipping CMD checks.. Version is less than 1.14..", recipe.getName()); + int slot = aligned.invSlotMap[i]; + ItemStack item = inv.getItem(slot); + if (item == null || item.getType() == Material.AIR) continue; - } - - // only if ignore model data is false - if (!recipe.getIgnoreModelData()) { - if ((!meta.hasCustomModelData() && ingredient.hasCustomModelData()) - || (meta.hasCustomModelData() && !ingredient.hasCustomModelData())) { - logDebug("[handleShaped] Skipping recipe..", recipe.getName()); - logDebug("[handleShaped] The recipe ingredient CMD and the inventory slot CMD do not match..", - recipe.getName()); - logDebug( - "[handleShaped] The inventory slot in question: " + i - + ". The ingredient slot in question: " + ingredient.getSlot(), - recipe.getName()); - logDebug("[handleShaped] Does the ingredient have CMD? " + ingredient.hasCustomModelData(), - recipe.getName()); - logDebug("[handleShaped] Does the inventory have CMD? " + meta.hasCustomModelData(), - recipe.getName()); - return false; - } - if (ingredient.hasCustomModelData() && meta.hasCustomModelData() - && (ingredient.getCustomModelData() != meta.getCustomModelData())) { - logDebug("[handleShaped] Skipping recipe..", recipe.getName()); - logDebug("[handleShaped] The ingredient CMD for the recipe and inventory do not match..", - recipe.getName()); - logDebug( - "[handleShaped] The inventory slot in question: " + i - + ". The ingredient slot in question: " + ingredient.getSlot(), - recipe.getName()); - logDebug("[handleShaped] The ingredient CMD: " + ingredient.getCustomModelData(), - recipe.getName()); - logDebug("[handleShaped] The inventory CMD: " + meta.getCustomModelData(), recipe.getName()); - return false; - } - logDebug("[handleShaped] Inventory and recipe ingredient CMD matched for slot " + i, - recipe.getName()); - } + int requiredAmount = Math.max(1, ing.getAmount()); + handlesItemRemoval(inv, recipe, item, ing, slot, itemsToRemove, itemsToAdd, requiredAmount); } - } - return true; - } - void handleAmountDeductions(CrafterCraftEvent e, String recipeName, Inventory inv) { - Recipe recipe = getRecipeUtil().getRecipe(recipeName); - ArrayList handledIngredients = new ArrayList(); - - int itemsToAdd = 1; - int itemsToRemove = 0; - for (RecipeUtil.Ingredient ingredient : recipe.getIngredients()) { - if (ingredient.isEmpty()) - continue; + } else { + // Shapeless + for (RecipeUtil.Ingredient ing : recipe.getIngredients()) { + if (ing.isEmpty()) + continue; - final Material material = ingredient.getMaterial(); - final String displayName = ingredient.getDisplayName(); - final int requiredAmount = Math.max(1, ingredient.getAmount()); - final boolean hasIdentifier = ingredient.hasIdentifier(); - - // === Your existing per-type logic === - if (recipe.getType() == RecipeType.SHAPELESS) { - logDebug("[handleShiftClicks] Found shapeless recipe to handle..", recipeName); + final Material mat = ing.getMaterial(); + final String name = ing.getDisplayName(); + final boolean hasID = ing.hasIdentifier(); + int requiredAmount = Math.max(1, ing.getAmount()); for (int i = 0; i < 9; i++) { - ItemStack item = inv.getItem(i); - int slot = i; + if (handledSlots[i]) + continue; - if (item == null || item.getType() == Material.AIR) + ItemStack item = inv.getItem(i); + if (item == null || item.getType() == Material.AIR || crafter.isSlotDisabled(i)) continue; - if (!AmountManager().matchesIngredient(item, recipeName, ingredient, material, displayName, - hasIdentifier)) + if (!AmountManager().matchesIngredient(item, recipeName, ing, mat, name, hasID)) continue; - // If your original removal didn’t require an explicit matchesIngredient() - // here, leave it as-is. (You earlier said only Pass 1 needs matching.) - if (!handledIngredients.contains(ingredient.getAbbreviation())) { - handlesItemRemoval(inv, recipe, item, ingredient, slot, itemsToRemove, itemsToAdd, - requiredAmount); - } + handlesItemRemoval(inv, recipe, item, ing, i, itemsToRemove, itemsToAdd, requiredAmount); + handledSlots[i] = true; } - if (!handledIngredients.contains(ingredient.getAbbreviation())) - handledIngredients.add(ingredient.getAbbreviation()); - - } else { - logDebug("[handleShiftClicks] Found other recipe type to handle..", recipeName); - int slot = ingredient.getSlot() - 1; - ItemStack item = inv.getItem(slot); - - if (item == null || item.getType() == Material.AIR) - continue; - - // Same note: keep your original behavior. - handlesItemRemoval(inv, recipe, item, ingredient, slot, itemsToRemove, itemsToAdd, requiredAmount); + handledIngredients.add(ing.getAbbreviation()); } } } - // Helper method for the handleShiftClick method - void handlesItemRemoval(Inventory inv, Recipe recipe, ItemStack item, RecipeUtil.Ingredient ingredient, int slot, - int itemsToRemove, int itemsToAdd, int requiredAmount) { + void handlesItemRemoval(Inventory inv, CraftingRecipeData recipe, ItemStack item, RecipeUtil.Ingredient ingredient, + int slot, int itemsToRemove, int itemsToAdd, int requiredAmount) { String recipeName = recipe.getName(); - logDebug("[handleShiftClicks] Checking slot " + slot + " for the recipe ", recipeName); - - if (AmountManager().matchesIngredient(item, recipeName, ingredient, ingredient.getMaterial(), - ingredient.getDisplayName(), ingredient.hasIdentifier())) { - - itemsToRemove = (itemsToAdd * requiredAmount) - 1; - - int availableItems = item.getAmount(); - - logDebug("[handleShiftClicks] Handling recipe " + recipeName, ""); - logDebug("[handleShiftClicks] ItemsToRemove: " + itemsToRemove + " - ItemsToAdd: " + itemsToAdd, ""); - logDebug("[handleShiftClicks] ItemAmount: " + availableItems, ""); - logDebug("[handleShiftClicks] RequiredAmount: " + requiredAmount, ""); - logDebug("[handleShiftClicks] Identifier: " + ingredient.getIdentifier() + " - HasIdentifier: " - + ingredient.hasIdentifier(), ""); - logDebug("[handleShiftClicks] Material: " + ingredient.getMaterial().toString(), ""); - logDebug("[handleShiftClicks] Displayname: " + ingredient.getDisplayName(), ""); - - if (availableItems < requiredAmount) - return; - - String id = ingredient.hasIdentifier() ? ingredient.getIdentifier() : item.getType().toString(); - if (recipe.isLeftover(id)) { - if (item.getType().toString().contains("_BUCKET")) - item.setType(XMaterial.BUCKET.parseMaterial()); - return; - } - if ((item.getAmount() - itemsToRemove) <= 0) { - inv.setItem(slot, null); - return; + if (!AmountManager().matchesIngredient(item, recipeName, ingredient, ingredient.getMaterial(), + ingredient.getDisplayName(), ingredient.hasIdentifier())) + return; + + itemsToRemove = (itemsToAdd * requiredAmount) - 1; // remove exact amount for the crafter + + int availableItems = item.getAmount(); + if (availableItems < requiredAmount) + return; + + logDebug("[handleShiftClicks] Handling recipe " + recipeName, ""); + logDebug("[handleShiftClicks] ItemsToRemove: " + (itemsToRemove + 1) + " - ItemsToAdd: " + itemsToAdd, ""); + logDebug("[handleShiftClicks] ItemAmount: " + availableItems, ""); + logDebug("[handleShiftClicks] RequiredAmount: " + requiredAmount, ""); + logDebug("[handleShiftClicks] Identifier: " + ingredient.getIdentifier() + " - HasIdentifier: " + + ingredient.hasIdentifier(), ""); + logDebug("[handleShiftClicks] Material: " + ingredient.getMaterial().toString(), ""); + logDebug("[handleShiftClicks] Displayname: " + ingredient.getDisplayName(), ""); + + String id = ingredient.hasIdentifier() ? ingredient.getIdentifier() : item.getType().toString(); + if (recipe.isLeftover(id)) { + if (item.getType().toString().contains("_BUCKET")) { + item.setType(XMaterial.BUCKET.parseMaterial()); } + return; + } - item.setAmount(item.getAmount() - itemsToRemove); + if ((item.getAmount() - itemsToRemove) <= 0) { + inv.setItem(slot, null); + return; } + + item.setAmount(item.getAmount() - itemsToRemove); } RecipeUtil getRecipeUtil() { return Main.getInstance().recipeUtil; } + ShapedChecks shapedChecks() { + return Main.getInstance().shapedChecks; + } + + ShapelessChecks shapelessChecks() { + return Main.getInstance().shapelessChecks; + } + CraftManager CraftManager() { return Main.getInstance().craftManager; } diff --git a/src/me/mehboss/crafting/ShapedChecks.java b/src/me/mehboss/crafting/ShapedChecks.java new file mode 100644 index 0000000..40e137f --- /dev/null +++ b/src/me/mehboss/crafting/ShapedChecks.java @@ -0,0 +1,273 @@ +package me.mehboss.crafting; + +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.bukkit.Material; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import me.mehboss.recipe.Main; +import me.mehboss.utils.CompatibilityUtil; +import me.mehboss.utils.RecipeUtil; +import me.mehboss.utils.RecipeUtil.Ingredient; +import me.mehboss.utils.RecipeUtil.Recipe; + +public class ShapedChecks { + + public class AlignedResult { + public final ItemStack[] getMatrix; + public final int[] invSlotMap; // maps aligned index → real inventory slot + + public AlignedResult(ItemStack[] alignedGrid, int[] invSlotMap) { + this.getMatrix = alignedGrid; + this.invSlotMap = invSlotMap; + } + } + + public AlignedResult getAlignedGrid(Inventory inv, List recipeIngredients) { + boolean isCraftingInventory = inv.getType() == InventoryType.WORKBENCH + || inv.getType() == InventoryType.CRAFTING; + + ItemStack[] invGrid = new ItemStack[9]; + int[] invSlotMap = new int[9]; // map each index to real inventory slot + + if (inv.getType() == InventoryType.CRAFTING) { + // 2×2 player crafting grid + int[] map = { 1, 2, -1, 3, 4, -1, -1, -1, -1 }; + + for (int i = 0; i < 9; i++) { + int real = map[i]; + if (real != -1) { + ItemStack item = inv.getItem(real); + invGrid[i] = (item == null ? new ItemStack(Material.AIR) : item); + invSlotMap[i] = real; + } + } + } else { + int idx = 0; + for (int slot = 0; slot < inv.getContents().length && idx < 9; slot++) { + if (isCraftingInventory && slot == 0) + continue; + + ItemStack item = inv.getItem(slot); + invGrid[idx] = (item == null ? new ItemStack(Material.AIR) : item); + invSlotMap[idx] = slot; // << real inventory slot + idx++; + } + } + + // Build 3×3 recipe grid + ItemStack[] recipeGrid = new ItemStack[9]; + for (int i = 0; i < 9; i++) { + Ingredient ing = recipeIngredients.get(i); + if (ing.isEmpty()) + recipeGrid[i] = new ItemStack(Material.AIR); + else if (ing.hasIdentifier()) + recipeGrid[i] = getRecipeUtil().getResultFromKey(ing.getIdentifier()); + else + recipeGrid[i] = new ItemStack(ing.getMaterialChoices().get(0)); + } + + // Trim recipe bounding box + int rTop = 3, rBottom = -1, rLeft = 3, rRight = -1; + for (int i = 0; i < 9; i++) { + if (recipeGrid[i].getType() != Material.AIR) { + int r = i / 3; + int c = i % 3; + rTop = Math.min(rTop, r); + rBottom = Math.max(rBottom, r); + rLeft = Math.min(rLeft, c); + rRight = Math.max(rRight, c); + } + } + + int recipeHeight = rBottom - rTop + 1; + int recipeWidth = rRight - rLeft + 1; + + // Try all shifts + for (int shiftRow = 0; shiftRow <= 3 - recipeHeight; shiftRow++) { + for (int shiftCol = 0; shiftCol <= 3 - recipeWidth; shiftCol++) { + + boolean match = true; + + for (int rr = 0; rr < recipeHeight && match; rr++) { + for (int rc = 0; rc < recipeWidth && match; rc++) { + + int invIndex = (shiftRow + rr) * 3 + (shiftCol + rc); + int recIndex = (rTop + rr) * 3 + (rLeft + rc); + + ItemStack invItem = invGrid[invIndex]; + Ingredient ing = recipeIngredients.get(recIndex); + + if (ing.isEmpty()) + continue; + + if (invItem == null || !ing.getMaterialChoices().contains(invItem.getType())) + match = false; + } + } + + if (match) { + ItemStack[] aligned = new ItemStack[9]; + int[] map = new int[9]; + + Arrays.fill(aligned, new ItemStack(Material.AIR)); + Arrays.fill(map, -1); + + // Copy matched region + for (int rr = 0; rr < recipeHeight; rr++) { + for (int rc = 0; rc < recipeWidth; rc++) { + + int invIndex = (shiftRow + rr) * 3 + (shiftCol + rc); + int recIndex = (rTop + rr) * 3 + (rLeft + rc); + + aligned[recIndex] = invGrid[invIndex]; + map[recIndex] = invSlotMap[invIndex]; // <<< real inventory slot + } + } + + return new AlignedResult(aligned, map); + } + } + } + + return null; + } + + public boolean handleShapedRecipe(InventoryType type, AlignedResult alignedGrid, Recipe recipe, + List recipeIngredients) { + int i = -1; + for (RecipeUtil.Ingredient ingredient : recipeIngredients) { + + i++; + ItemStack stack = alignedGrid.getMatrix[i]; + if (ingredient.isEmpty()) { + if (stack.getType() != Material.AIR) + return false; + + continue; + } + + ItemMeta meta = stack.getItemMeta(); + // IDENTIFIER CHECK + if (ingredient.hasIdentifier()) { + String invID = getRecipeUtil().getKeyFromResult(stack); + + if (invID == null || !invID.equals(ingredient.getIdentifier())) { + logDebug("[handleShaped] Skipping recipe..", recipe.getName()); + logDebug("[handleShaped] IDs do not match", recipe.getName()); + logDebug("[handleShaped] IngID = " + ingredient.getIdentifier(), recipe.getName()); + logDebug("[handleShaped] InvID = " + invID, recipe.getName()); + return false; + } + + logDebug("[handleShaped] Passed all checks for the ingredient in slot " + i, recipe.getName()); + continue; + } + + logDebug("[handleShaped] Ingredient slot " + i + " does not have an identifier..", recipe.getName()); + + if (recipe.getIgnoreData()) { + logDebug("[handleShaped] Skipping data checks.. Ignore-Data is enabled..", recipe.getName()); + continue; + } + + // NAME CHECKS + if (!recipe.getIgnoreNames()) { + + if ((!CompatibilityUtil.hasDisplayname(meta) && ingredient.hasDisplayName()) + || (CompatibilityUtil.hasDisplayname(meta) && !ingredient.hasDisplayName())) { + logDebug("[handleShaped] Skipping recipe..", recipe.getName()); + logDebug( + "[handleShaped] The recipe ingredient displayname and the inventory slot displayname do not match", + recipe.getName()); + logDebug("[handleShaped] The inventory slot in question: " + i + + ". The ingredient slot in question: " + ingredient.getSlot(), recipe.getName()); + logDebug("[handleShaped] Does the ingredient have a displayname? " + ingredient.hasDisplayName(), + recipe.getName()); + logDebug("[handleShaped] Does the inventory have a displayname? " + meta.hasDisplayName(), + recipe.getName()); + return false; + } + + if (ingredient.hasDisplayName() && CompatibilityUtil.hasDisplayname(meta) + && !(ingredient.getDisplayName().equals(CompatibilityUtil.getDisplayname(meta)))) { + logDebug("[handleShaped] Skipping recipe..", recipe.getName()); + logDebug("[handleShaped] The ingredient name for the recipe and inventory do not match", + recipe.getName()); + logDebug("[handleShaped] The inventory slot in question: " + i + + ". The ingredient slot in question: " + ingredient.getSlot(), recipe.getName()); + logDebug("[handleShaped] The ingredient displayname: " + ingredient.getDisplayName(), + recipe.getName()); + logDebug("[handleShaped] The inventory displayname: " + meta.getDisplayName(), recipe.getName()); + return false; + } + + logDebug("[handleShaped] Inventory and recipe ingredient displayname matched for slot " + i, + recipe.getName()); + } + + // MODEL DATA CHECKS + if (!Main.getInstance().serverVersionAtLeast(1, 14)) { + logDebug("[handleShaped] Skipping CMD checks.. Version is less than 1.14..", recipe.getName()); + continue; + } + + if (!recipe.getIgnoreModelData()) { + + if ((!meta.hasCustomModelData() && ingredient.hasCustomModelData()) + || (meta.hasCustomModelData() && !ingredient.hasCustomModelData())) { + logDebug("[handleShaped] Skipping recipe..", recipe.getName()); + logDebug("[handleShaped] The recipe ingredient CMD and the inventory slot CMD do not match..", + recipe.getName()); + logDebug("[handleShaped] The inventory slot in question: " + i + + ". The ingredient slot in question: " + ingredient.getSlot(), recipe.getName()); + logDebug("[handleShaped] Does the ingredient have CMD? " + ingredient.hasCustomModelData(), + recipe.getName()); + logDebug("[handleShaped] Does the inventory have CMD? " + meta.hasCustomModelData(), + recipe.getName()); + return false; + } + + if (ingredient.hasCustomModelData() && meta.hasCustomModelData() + && (ingredient.getCustomModelData() != meta.getCustomModelData())) { + logDebug("[handleShaped] Skipping recipe..", recipe.getName()); + logDebug("[handleShaped] The ingredient CMD for the recipe and inventory do not match..", + recipe.getName()); + logDebug("[handleShaped] The inventory slot in question: " + i + + ". The ingredient slot in question: " + ingredient.getSlot(), recipe.getName()); + logDebug("[handleShaped] The ingredient CMD: " + ingredient.getCustomModelData(), recipe.getName()); + logDebug("[handleShaped] The inventory CMD: " + meta.getCustomModelData(), recipe.getName()); + return false; + } + + logDebug("[handleShaped] Inventory and recipe ingredient CMD matched for slot " + i, recipe.getName()); + } + } + + return true; + } + + RecipeUtil getRecipeUtil() { + return Main.getInstance().recipeUtil; + } + + void logDebug(String st, String recipeName, UUID id) { + if (Main.getInstance().debug && (id == null || (!Main.getInstance().inInventory.contains(id)))) + Logger.getLogger("Minecraft").log(Level.WARNING, + "[DEBUG][" + Main.getInstance().getName() + "][Crafting][" + recipeName + "]" + st); + } + + void logDebug(String st, String recipeName) { + if (Main.getInstance().debug) + Logger.getLogger("Minecraft").log(Level.WARNING, + "[DEBUG][" + Main.getInstance().getName() + "][Crafting][" + recipeName + "]" + st); + } + +} diff --git a/src/me/mehboss/crafting/ShapelessChecks.java b/src/me/mehboss/crafting/ShapelessChecks.java new file mode 100644 index 0000000..13e00ae --- /dev/null +++ b/src/me/mehboss/crafting/ShapelessChecks.java @@ -0,0 +1,255 @@ +package me.mehboss.crafting; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.bukkit.Material; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import me.mehboss.recipe.Main; +import me.mehboss.utils.CompatibilityUtil; +import me.mehboss.utils.RecipeUtil; +import me.mehboss.utils.RecipeUtil.Recipe; + +public class ShapelessChecks { + + public boolean handleShapelessRecipe(Inventory inv, Recipe recipe, List recipeIngredients, + UUID id) { + + logDebug("[handleShapeless] Handling shapeless checks..!", recipe.getName(), id); + + ArrayList slotNames = new ArrayList<>(); + ArrayList recipeNames = new ArrayList<>(); + + ArrayList recipeMD = new ArrayList<>(); + ArrayList inventoryMD = new ArrayList<>(); + + ArrayList slotIDs = new ArrayList<>(); + ArrayList recipeIDs = new ArrayList<>(); + + Map recipeCount = new HashMap<>(); + Map inventoryCount = new HashMap<>(); + + if (inv.getType() == InventoryType.CRAFTING) { + inventoryCount.put(Material.AIR, 5); + Collections.addAll(slotNames, "null", "null", "null", "null", "null"); + Collections.addAll(slotIDs, "null", "null", "null", "null", "null"); + Collections.addAll(inventoryMD, -1, -1, -1, -1, -1); + } + + Boolean isCrafting = inv.getType() == InventoryType.CRAFTING || inv.getType() == InventoryType.WORKBENCH; + ItemStack[] matrix = inv.getContents(); + + for (int i = 0; i < matrix.length; i++) { + if (i == 0 && isCrafting) + continue; + + ItemStack it = matrix[i]; + + if (it == null || it.getType() == Material.AIR || it.getAmount() <= 0) { + slotNames.add("null"); + slotIDs.add("null"); + inventoryMD.add(-1); + inventoryCount.put(Material.AIR, inventoryCount.getOrDefault(Material.AIR, 0) + 1); + continue; + } + + String key = getRecipeUtil().getKeyFromResult(it); + if (key == null) + key = "null"; + slotIDs.add(key); + + if (Main.getInstance().serverVersionAtLeast(1, 14)) { + if (it.hasItemMeta() && it.getItemMeta().hasCustomModelData()) { + inventoryMD.add(it.getItemMeta().getCustomModelData()); + } else { + inventoryMD.add(-1); + } + } + + inventoryCount.put(it.getType(), inventoryCount.getOrDefault(it.getType(), 0) + 1); + + if (!it.hasItemMeta() || !CompatibilityUtil.hasDisplayname(it.getItemMeta())) { + slotNames.add("false"); + continue; + } + + slotNames.add(CompatibilityUtil.getDisplayname(it.getItemMeta())); + } + + // working copy used for consumption while matching + Map availableCount = new HashMap<>(inventoryCount); + + for (RecipeUtil.Ingredient ingredient : recipeIngredients) { + if (ingredient.isEmpty()) { + recipeCount.put(Material.AIR, recipeCount.getOrDefault(Material.AIR, 0) + 1); + recipeNames.add("null"); + recipeMD.add(-1); + recipeIDs.add("null"); + continue; + } + + if (ingredient.hasIdentifier() && getRecipeUtil().getResultFromKey(ingredient.getIdentifier()) != null) { + ItemStack exactMatch = getRecipeUtil().getResultFromKey(ingredient.getIdentifier()); + Material type = exactMatch.getType(); + + recipeCount.put(type, recipeCount.getOrDefault(type, 0) + 1); + availableCount.put(type, availableCount.getOrDefault(type, 0) - 1); + + recipeIDs.add(ingredient.getIdentifier()); + + if (Main.getInstance().serverVersionAtLeast(1, 14)) { + if (exactMatch.hasItemMeta() && exactMatch.getItemMeta().hasCustomModelData()) { + recipeMD.add(exactMatch.getItemMeta().getCustomModelData()); + } else { + recipeMD.add(-1); + } + } + + if (CompatibilityUtil.hasDisplayname(exactMatch.getItemMeta())) { + recipeNames.add(CompatibilityUtil.getDisplayname(exactMatch.getItemMeta())); + } else { + recipeNames.add("false"); + } + continue; + } + + if (ingredient.hasMaterialChoices()) { + boolean matched = false; + for (Material choice : ingredient.getMaterialChoices()) { + int avail = availableCount.getOrDefault(choice, 0); + if (avail > 0) { + availableCount.put(choice, avail - 1); + recipeCount.put(choice, recipeCount.getOrDefault(choice, 0) + 1); + matched = true; + break; + } + } + if (!matched) { + return false; + } + } else { + Material mat = ingredient.getMaterial(); + recipeCount.put(mat, recipeCount.getOrDefault(mat, 0) + 1); + availableCount.put(mat, availableCount.getOrDefault(mat, 0) - 1); + } + + recipeIDs.add("null"); + recipeMD.add(ingredient.getCustomModelData()); + + if (ingredient.hasDisplayName()) { + recipeNames.add(ingredient.getDisplayName()); + } else { + recipeNames.add("false"); + } + } + + boolean hasIngredients = true; + for (Map.Entry entry : recipeCount.entrySet()) { + Material material = entry.getKey(); + int requiredCount = entry.getValue(); + int invCount = inventoryCount.getOrDefault(material, 0); + + if (invCount < requiredCount) { + hasIngredients = false; + break; + } + } + + if (!hasIngredients) { + logDebug("[handleShapeless] Missing required ingredients! Skipping recipe..", recipe.getName(), id); + return false; + } + logDebug("[handleShapeless] All required ingredients found..", recipe.getName(), id); + + if (!recipeIDs.equals(slotIDs) || !slotIDs.equals(recipeIDs)) { + logDebug("[handleShapeless] NBT tags mismatch: recipe vs inventory", recipe.getName(), id); + logDebug("[handleShapeless] recipeIDS: " + recipeIDs, recipe.getName(), id); + logDebug("[handleShapeless] slotIDS: " + slotIDs, recipe.getName(), id); + return false; + } + + if (recipe.getIgnoreData()) + return true; + + if (!recipe.getIgnoreModelData()) { + Map recipeModelCount = new HashMap<>(); + Map inventoryModelCount = new HashMap<>(); + + if (!Main.getInstance().serverVersionAtLeast(1, 14)) { + recipeMD.clear(); + inventoryMD.clear(); + } + for (int model : recipeMD) { + recipeModelCount.put(model, recipeModelCount.getOrDefault(model, 0) + 1); + } + for (int model : inventoryMD) { + inventoryModelCount.put(model, inventoryModelCount.getOrDefault(model, 0) + 1); + } + + if (!recipeMD.containsAll(inventoryMD) || !inventoryMD.containsAll(recipeMD)) { + logDebug("[handleShapeless] Model data mismatch: recipe vs inventory", recipe.getName(), id); + return false; + } + + if (!recipeModelCount.equals(inventoryModelCount)) { + logDebug("[handleShapeless] Model data mismatch: recipe vs inventory", recipe.getName(), id); + logDebug(" RM: " + recipeModelCount, recipe.getName(), id); + logDebug(" IM: " + inventoryModelCount, recipe.getName(), id); + return false; + } + } + + if (!recipe.getIgnoreNames()) { + Map recipeNameCount = new HashMap<>(); + Map slotNameCount = new HashMap<>(); + + for (String name : recipeNames) { + recipeNameCount.put(name, recipeNameCount.getOrDefault(name, 0) + 1); + } + for (String name : slotNames) { + slotNameCount.put(name, slotNameCount.getOrDefault(name, 0) + 1); + } + + if (!recipeNames.containsAll(slotNames) || !slotNames.containsAll(recipeNames)) { + logDebug("[handleShapeless] Display name mismatch: recipe vs inventory", recipe.getName(), id); + logDebug(slotNames.toString(), recipe.getName(), id); + logDebug(recipeNames.toString(), recipe.getName(), id); + return false; + } + + if (!recipeNameCount.equals(slotNameCount)) { + logDebug("[handleShapeless] Display name count mismatch: recipe vs inventory", recipe.getName(), id); + logDebug(" RN: " + recipeNameCount, recipe.getName(), id); + logDebug(" IN: " + slotNameCount, recipe.getName(), id); + return false; + } + } + + return true; + } + + RecipeUtil getRecipeUtil() { + return Main.getInstance().recipeUtil; + } + + void logDebug(String st, String recipeName, UUID id) { + if (Main.getInstance().debug && (id == null || (!Main.getInstance().inInventory.contains(id)))) + Logger.getLogger("Minecraft").log(Level.WARNING, + "[DEBUG][" + Main.getInstance().getName() + "][Crafting][" + recipeName + "]" + st); + } + + void logDebug(String st, String recipeName) { + if (Main.getInstance().debug) + Logger.getLogger("Minecraft").log(Level.WARNING, + "[DEBUG][" + Main.getInstance().getName() + "][Crafting][" + recipeName + "]" + st); + } +} diff --git a/src/me/mehboss/crafting/package-info.java b/src/me/mehboss/crafting/package-info.java new file mode 100644 index 0000000..585d129 --- /dev/null +++ b/src/me/mehboss/crafting/package-info.java @@ -0,0 +1,7 @@ +/** + * Houses all logic used for custom crafting table recipes. + *

    + * Includes shaped and shapeless recipe handling, leftover items, + * crafting conditions, and integration hooks for recipe registration. + */ +package me.mehboss.crafting; \ No newline at end of file diff --git a/src/me/mehboss/gui/BookGUI.java b/src/me/mehboss/gui/BookGUI.java index f876649..1e28c02 100644 --- a/src/me/mehboss/gui/BookGUI.java +++ b/src/me/mehboss/gui/BookGUI.java @@ -12,6 +12,10 @@ import me.mehboss.utils.RecipeUtil; import me.mehboss.utils.RecipeUtil.Recipe; import me.mehboss.utils.RecipeUtil.Recipe.RecipeType; +import me.mehboss.utils.data.BrewingRecipeData; +import me.mehboss.utils.data.CookingRecipeData; +import me.mehboss.utils.data.CraftingRecipeData; +import me.mehboss.utils.data.WorkstationRecipeData; import org.bukkit.Bukkit; import org.bukkit.ChatColor; @@ -30,6 +34,9 @@ import org.bukkit.plugin.Plugin; import com.cryptomorin.xseries.XMaterial; + +import me.mehboss.gui.framework.RecipeGUI; +import me.mehboss.gui.framework.GuiView.GuiRegistry; import me.mehboss.recipe.Main; import me.mehboss.utils.CompatibilityUtil; @@ -53,7 +60,7 @@ public BookGUI(Plugin p) { getConfig().getString("gui.Displayname").replace("%page%", "1"))); } - private List buildRecipesFor(Player p, RecipeType type) { + public List buildRecipesFor(Player p, RecipeType type) { HashMap recipesMap = (type != null) ? Main.getInstance().recipeUtil.getRecipesFromType(type) : Main.getInstance().recipeUtil.getAllRecipes(); @@ -61,7 +68,7 @@ private List buildRecipesFor(Player p, RecipeType type) { if (recipesMap == null || recipesMap.isEmpty()) return new ArrayList<>(); - return recipesMap.values().stream().filter(r -> !r.hasPerm() || p.hasPermission(r.getPerm())) + return recipesMap.values().stream().filter(r -> r.isActive() && (!r.hasPerm() || p.hasPermission(r.getPerm()))) .collect(Collectors.toList()); } @@ -131,26 +138,53 @@ private RecipeType parseTypeFromHeader(ItemStack header) { return null; String name = ChatColor.stripColor(m.getDisplayName()).toUpperCase().split(" ")[0]; - return RecipeType.valueOf(name); + return RecipeType.fromString(name); } - public void showCreationMenu(Inventory inv, ItemStack item, Player p, String recipeName, String perm, - Boolean creating, Boolean viewing, RecipeType type) { - Inventory cInv = null; + public void showCreationMenu(Player player, Recipe rawRecipe, boolean creating, boolean viewing) { - if (!(Main.getInstance().recipeBook.contains(p.getUniqueId()))) { - cInv = Bukkit.getServer().createInventory(null, 54, - ChatColor.translateAlternateColorCodes('&', "&cEDITING: " + recipeName)); - } else { - cInv = Bukkit.getServer().createInventory(null, 54, - ChatColor.translateAlternateColorCodes('&', "&cVIEWING: " + recipeName)); + RecipeGUI gui = Main.getInstance().editItem; + Recipe recipe = rawRecipe; + + if (creating) { + RecipeType type = rawRecipe.getType(); + String id = rawRecipe.getName(); + switch (type) { + + case SHAPED: + case SHAPELESS: + recipe = new CraftingRecipeData(id); + break; + + case FURNACE: + case BLASTFURNACE: + case SMOKER: + case CAMPFIRE: + recipe = new CookingRecipeData(id); + break; + + case STONECUTTER: + case GRINDSTONE: + case ANVIL: + recipe = new WorkstationRecipeData(id); + break; + + case BREWING_STAND: + recipe = new BrewingRecipeData(id); + break; + + default: + throw new IllegalArgumentException("Unsupported recipe type: " + type); + } } - RecipeGUI.getInstance().setItems(creating, viewing, cInv, recipeName, perm, item, p, type); - p.openInventory(cInv); + recipe.setType(rawRecipe.getType()); - if (inv != null) - Main.getInstance().saveInventory.put(p.getUniqueId(), inv); + if (viewing) { + gui.openViewing(player, recipe); + } else { + gui.openEditing(player, recipe); + } } @EventHandler @@ -178,29 +212,25 @@ void onClick(InventoryClickEvent e) { if (e.getCurrentItem() == null || e.getCurrentItem().getType() == Material.AIR) return; - // --- Create button - if (e.getCurrentItem().getType() != XMaterial.BLACK_STAINED_GLASS_PANE.parseMaterial() + ItemStack stained = createItem("stainedG", XMaterial.BLACK_STAINED_GLASS_PANE, " "); + if (!e.getCurrentItem().equals(stained) && (e.getRawSlot() == 3 || e.getRawSlot() == 5)) { - showCreationMenu(e.getInventory(), null, p, "", null, true, false, type); + Recipe recipe = new Recipe("New Recipe"); + recipe.setType(type); + showCreationMenu(p, recipe, true, false); return; } - // --- Main menu button if (e.getRawSlot() == 49) { - if (Main.getInstance().saveInventory.containsKey(p.getUniqueId())) { - p.openInventory(Main.getInstance().saveInventory.get(p.getUniqueId())); - Main.getInstance().saveInventory.remove(p.getUniqueId()); - } else { - Main.getInstance().typeGUI.open(p); - } + Main.getInstance().typeGUI.open(p); return; } - // --- Paging (Prev 48 / Next 50) --- if (e.getRawSlot() == 48 || e.getRawSlot() == 50) { - String original = CompatibilityUtil.getTitle(e); + + String original = title; String[] cp = original.split("Page "); - int currentpage = Integer.valueOf(cp[1]); + int currentpage = Integer.parseInt(cp[1]); int newpage = currentpage; List recipes = buildRecipesFor(p, type); @@ -222,6 +252,7 @@ void onClick(InventoryClickEvent e) { original.replace(String.valueOf(currentpage), String.valueOf(newpage))); Inventory newp = Bukkit.getServer().createInventory(null, 54, newname); + ItemStack header = (type == null) ? createTypeHeader(null) : createTypeHeader(type); items(p, newp, newpage - 1, recipes, header); @@ -241,18 +272,13 @@ void onClick(InventoryClickEvent e) { } } - logDebug("[RecipeBooklet][" + p.getName() + "] Triggered open recipe matrix.. " + recipeName); - if (recipeName != null) { - Boolean viewing = false; - if (Main.getInstance().recipeBook.contains(p.getUniqueId())) - viewing = true; + boolean viewing = Main.getInstance().recipeBook.contains(p.getUniqueId()); + Recipe recipe = Main.getInstance().recipeUtil.getRecipe(recipeName); - logDebug("[RecipeBooklet][" + p.getName() + "] Opening recipe matrix.. " + recipeName); - showCreationMenu(e.getInventory(), e.getCurrentItem(), p, recipeName, null, false, viewing, null); + showCreationMenu(p, recipe, false, viewing); } } - return; } } @@ -360,8 +386,20 @@ ItemStack createItem(String id, XMaterial material, String name, String... lore) public void openType(Player p, RecipeType type) { List recipes = buildRecipesFor(p, type); - if (recipes.isEmpty()) + boolean viewing = Main.getInstance().recipeBook.contains(p.getUniqueId()); + + if (recipes.isEmpty()) { + if (!viewing) { + Recipe recipe = new Recipe("New Recipe"); + recipe.setType(type); + showCreationMenu(p, recipe, true, false); + } return; + } + + boolean hasRoot = GuiRegistry.hasRootView(p.getUniqueId()); + if (hasRoot) + GuiRegistry.clearRootView(p.getUniqueId()); String title = ChatColor.translateAlternateColorCodes('&', Main.getInstance().getConfig().getString("gui.Displayname").replace("%page%", "1")); diff --git a/src/me/mehboss/gui/RecipeGUI.java b/src/me/mehboss/gui/RecipeGUI.java deleted file mode 100644 index 4ec59c5..0000000 --- a/src/me/mehboss/gui/RecipeGUI.java +++ /dev/null @@ -1,921 +0,0 @@ -package me.mehboss.gui; - -import java.io.File; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.UUID; -import java.util.logging.Level; -import java.util.logging.Logger; - -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.Material; -import org.bukkit.OfflinePlayer; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.event.inventory.InventoryType; -import org.bukkit.event.player.AsyncPlayerChatEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.plugin.Plugin; - -import com.cryptomorin.xseries.XMaterial; - -import me.clip.placeholderapi.PlaceholderAPI; -import me.mehboss.recipe.Main; -import me.mehboss.utils.CompatibilityUtil; -import me.mehboss.utils.RecipeUtil; -import me.mehboss.utils.RecipeUtil.Ingredient; -import me.mehboss.utils.RecipeUtil.Recipe; -import me.mehboss.utils.RecipeUtil.Recipe.RecipeType; - -public class RecipeGUI implements Listener { - - // *** this menu is the GUI for adding a new recipe - public HashMap editmeta = new HashMap<>(); - HashMap inventoryinstance = new HashMap<>(); - HashMap getnewid = new HashMap<>(); - - List lore = new ArrayList<>(); - - private static RecipeGUI instance; - - Plugin config = Bukkit.getPluginManager().getPlugin("CustomRecipes"); - RecipeSaver saveChanges; - - public RecipeGUI(Plugin p, String item) { - instance = this; - saveChanges = new RecipeSaver(); - } - - public static RecipeGUI getInstance() { - return instance; - } - - RecipeUtil getRecipeUtil() { - return Main.getInstance().recipeUtil; - } - - FileConfiguration getConfig(String recipeName) { - File dataFolder = Main.getInstance().getDataFolder(); - File recipesFolder = new File(dataFolder, "recipes"); - File recipeFile = new File(recipesFolder, recipeName + ".yml"); - return YamlConfiguration.loadConfiguration(recipeFile); - } - - private static final class RecipeLayout { - final int[] ingredientSlots; // where ingredients go (order matters for SHAPED) - final Integer fuelSlot; // null when no fuel slot is used - final int resultSlot; // output slot - - RecipeLayout(int[] ingredientSlots, Integer fuelSlot, int resultSlot) { - this.ingredientSlots = ingredientSlots; - this.fuelSlot = fuelSlot; - this.resultSlot = resultSlot; - } - - boolean usesFuel() { - return fuelSlot != null; - } - } - - private static final HashMap LAYOUTS = new HashMap<>(); - static { - int[] CRAFTING_GRID = { 11, 12, 13, 20, 21, 22, 29, 30, 31 }; - - // Crafting - LAYOUTS.put(RecipeType.SHAPED, new RecipeLayout(CRAFTING_GRID, null, 24)); - LAYOUTS.put(RecipeType.SHAPELESS, new RecipeLayout(CRAFTING_GRID, null, 24)); - - // Single-input machines (no fuel) - LAYOUTS.put(RecipeType.STONECUTTER, new RecipeLayout(new int[] { 20 }, null, 24)); - LAYOUTS.put(RecipeType.CAMPFIRE, new RecipeLayout(new int[] { 20 }, null, 24)); - - // Furnace-like (input at 11, fuel 29, result 24) - LAYOUTS.put(RecipeType.FURNACE, new RecipeLayout(new int[] { 11 }, 29, 24)); - LAYOUTS.put(RecipeType.BLASTFURNACE, new RecipeLayout(new int[] { 11 }, 29, 24)); - LAYOUTS.put(RecipeType.SMOKER, new RecipeLayout(new int[] { 11 }, 29, 24)); - - // Two-input utilities with a center gap (20 [gap 21] 22) - LAYOUTS.put(RecipeType.ANVIL, new RecipeLayout(new int[] { 20, 22 }, null, 24)); - LAYOUTS.put(RecipeType.GRINDSTONE, new RecipeLayout(new int[] { 20, 22 }, null, 24)); - } - - public void setItems(Boolean creating, Boolean viewing, Inventory i, String invname, String perm, ItemStack item, - OfflinePlayer p, RecipeType rType) { - - String configname = invname; - - // Get recipe and decide layout from its type - Recipe r = Main.getInstance().getRecipeUtil().getRecipe(configname); - RecipeType type = (r != null && r.getType() != null) ? r.getType() : rType; - RecipeLayout layout = LAYOUTS.getOrDefault(type, LAYOUTS.get(RecipeType.SHAPED)); - - List loreList = new ArrayList<>(); - - final int[] controlSlots = { 0, 7, 8, 9, 26, 35, 44, 45, 48, 49, 50, 52, 53 }; - - boolean[] paneAt = new boolean[54]; - for (int s = 0; s < 54; s++) - paneAt[s] = true; - paneAt[layout.resultSlot] = false; - for (int s : layout.ingredientSlots) - paneAt[s] = false; - if (layout.usesFuel()) - paneAt[layout.fuelSlot] = false; - - // gap between two-input utilities (center slot 21) - boolean isTwoInputUtility = (type == RecipeType.ANVIL || type == RecipeType.GRINDSTONE); - if (isTwoInputUtility) - paneAt[21] = true; - - // In EDITING mode, keep control slots empty (they'll be populated by widgets); - // in VIEWING mode, we want them to be panes except the Back buttons. - if (!viewing) { - for (int s : controlSlots) - paneAt[s] = false; - } - - ItemStack stained = XMaterial.BLACK_STAINED_GLASS_PANE.parseItem(); - ItemMeta stainedm = stained.getItemMeta(); - stainedm.setDisplayName(" "); - stained.setItemMeta(stainedm); - for (int s = 0; s < 54; s++) - if (paneAt[s]) - i.setItem(s, stained); - - // not creating new recipe, fill in slots - if (!creating) { - int ingIndex = 0; - Recipe recipe = Main.getInstance().getRecipeUtil().getRecipe(configname); - - for (Ingredient ingredient : recipe.getIngredients()) { - if (recipe.getType() != RecipeType.SHAPED && (ingredient == null || ingredient.isEmpty())) { - continue; // skip empties, don't advance ingIndex - } - - if (ingIndex >= layout.ingredientSlots.length) - break; - - boolean foundIdentifier = itemIngredients(ingredient, configname, ingIndex + 1, p) != null; - ItemStack mat = foundIdentifier ? itemIngredients(ingredient, configname, ingIndex + 1, p) - : XMaterial.matchXMaterial(ingredient.getMaterial().toString()).get().parseItem(); - - // for legacy versions for short values - if (!foundIdentifier && ingredient.hasMaterialData()) - mat = ingredient.getMaterialData().toItemStack(); - - mat.setAmount(ingredient.getAmount()); - - if (!foundIdentifier) { - ItemMeta matm = mat.getItemMeta(); - if (ingredient.hasDisplayName()) { - matm.setDisplayName(ingredient.getDisplayName()); - } - mat.setItemMeta(matm); - } - - i.setItem(layout.ingredientSlots[ingIndex], mat); - ingIndex++; - } - - ItemStack result = new ItemStack(Main.getInstance().recipeUtil.getRecipe(configname).getResult()); - ItemMeta resultM = result.getItemMeta(); - - if (result.hasItemMeta() && resultM.hasLore() && hasPlaceholder()) { - resultM.setLore(PlaceholderAPI.setPlaceholders(p, resultM.getLore())); - result.setItemMeta(resultM); - } - - i.setItem(layout.resultSlot, result); - } - - // VIEWING mode: turn all control slots into panes, then add Back buttons - if (viewing) { - for (int s : controlSlots) { - if (s == 48 || s == 49 || s == 50) - continue; // back buttons set below - i.setItem(s, stained); - } - - // Three "Back" buttons centered bottom - ItemStack back = XMaterial.RED_STAINED_GLASS_PANE.parseItem(); - ItemMeta backm = back.getItemMeta(); - backm.setDisplayName(ChatColor.DARK_RED + "Back"); - back.setItemMeta(backm); - i.setItem(48, back); - i.setItem(49, back); - i.setItem(50, back); - - return; - } - - /* ====== below here are the editor widgets (only when not viewing) ====== */ - - ItemStack identifier = XMaterial.PAPER.parseItem(); - ItemMeta identifierm = identifier.getItemMeta(); - identifierm.setDisplayName(ChatColor.translateAlternateColorCodes('&', "&fIdentifier:&7 ")); - loreList.add(ChatColor.GRAY + getConfig(configname).getString(configname + ".Identifier")); - - if (creating && !invname.isEmpty() && !invname.equals("")) { - loreList.clear(); - loreList.add(ChatColor.GRAY + invname); - } - - identifierm.setLore(loreList); - identifier.setItemMeta(identifierm); - loreList.clear(); - - ItemStack lore = XMaterial.BOOK.parseItem(); - ItemMeta lorem = lore.getItemMeta(); - lorem.setDisplayName(ChatColor.translateAlternateColorCodes('&', "&fLore")); - - if (item != null && item.hasItemMeta() && item.getItemMeta().hasLore()) { - lorem.setLore(item.getItemMeta().getLore()); - if (hasPlaceholder()) - lorem.setLore(PlaceholderAPI.setPlaceholders(p, item.getItemMeta().getLore())); - } - lore.setItemMeta(lorem); - - ItemStack effects = XMaterial.GHAST_TEAR.parseItem(); - ItemMeta effectsm = effects.getItemMeta(); - effectsm.setDisplayName(ChatColor.translateAlternateColorCodes('&', "&fEffects")); - if (getConfig(configname).getString(configname + ".Effects") != null) { - for (String effectslore : getConfig(configname).getStringList(configname + ".Effects")) { - loreList.add(ChatColor.GRAY + effectslore); - } - effectsm.setLore(loreList); - } - effects.setItemMeta(effectsm); - loreList.clear(); - - ItemStack converter = XMaterial.FURNACE.parseItem(); - ItemMeta converterm = effects.getItemMeta(); // keep original behavior - converterm.setDisplayName(ChatColor.translateAlternateColorCodes('&', "&fConverter")); - loreList.add(ChatColor.GRAY + type.toString().toUpperCase()); - converterm.setLore(loreList); - converter.setItemMeta(converterm); - loreList.clear(); - - ItemStack permission = XMaterial.ENDER_PEARL.parseItem(); - ItemMeta permissionm = permission.getItemMeta(); - permissionm.setDisplayName(ChatColor.translateAlternateColorCodes('&', "&fPermission: &7")); - - if (getConfig(configname).isSet(configname + ".Permission") - && !getConfig(configname).getString(configname + ".Permission").equals("none")) - loreList.add(ChatColor.GRAY + getConfig(configname).getString(configname + ".Permission")); - - if (perm != null) - loreList.add(ChatColor.GRAY + perm); - - permissionm.setLore(loreList); - permission.setItemMeta(permissionm); - loreList.clear(); - - ItemStack hideenchants = XMaterial.DIRT.parseItem(); - ItemMeta hideenchantsm = hideenchants.getItemMeta(); - Boolean hc = getConfig(configname).getBoolean(configname + ".Placeable"); - String ht = "&cfalse"; - if (hc == true) - ht = "&atrue"; - hideenchantsm.setDisplayName(ChatColor.translateAlternateColorCodes('&', "&fPlaceable: &f" + ht)); - hideenchants.setItemMeta(hideenchantsm); - - // Shapeless toggle only for crafting types - boolean showShapeless = (type == RecipeType.SHAPED || type == RecipeType.SHAPELESS); - ItemStack shapeless = XMaterial.CRAFTING_TABLE.parseItem(); - if (showShapeless) { - shapeless = XMaterial.CRAFTING_TABLE.parseItem(); - ItemMeta shapelessm = shapeless.getItemMeta(); - Boolean isShapeless = creating ? rType.equals(RecipeType.SHAPELESS) : true; - Boolean sc = creating ? isShapeless : getConfig(configname).getBoolean(configname + ".Shapeless"); - String toggle = sc ? "&atrue" : "&cfalse"; - shapelessm.setDisplayName(ChatColor.translateAlternateColorCodes('&', "&fShapeless: &f" + toggle)); - shapeless.setItemMeta(shapelessm); - } - - // Exact choice - ItemStack ec = XMaterial.LEVER.parseItem(); - ItemMeta ecm = ec.getItemMeta(); - Boolean exact = getConfig(configname).getBoolean(configname + ".Exact-Choice", true); - String toggle = exact ? "&atrue" : "&cfalse"; - ecm.setDisplayName(ChatColor.translateAlternateColorCodes('&', "&fExact-Choice: &f" + toggle)); - ec.setItemMeta(ecm); - - ItemStack name = XMaterial.WRITABLE_BOOK.parseItem(); - ItemMeta namem = name.getItemMeta(); - namem.setDisplayName(ChatColor.WHITE + "Recipe Name: "); - if (getConfig(configname).isConfigurationSection(configname)) - loreList.add(ChatColor.GRAY + configname); - - if (creating && !invname.isEmpty() && !invname.equals("")) { - loreList.clear(); - loreList.add(ChatColor.GRAY + invname); - } - - namem.setLore(loreList); - name.setItemMeta(namem); - loreList.clear(); - - ItemStack amount = XMaterial.FEATHER.parseItem(); - ItemMeta amountm = amount.getItemMeta(); - amountm.setDisplayName(ChatColor.translateAlternateColorCodes('&', - "&fAmount: &7" + getConfig(configname).getString(configname + ".Amount"))); - amount.setItemMeta(amountm); - - i.setItem(9, identifier); - i.setItem(8, hideenchants); - i.setItem(7, effects); - i.setItem(17, converter); - i.setItem(26, permission); - i.setItem(0, name); - i.setItem(35, amount); - i.setItem(44, lore); - i.setItem(18, ec); - - // Slot 53 – shapeless only shown for crafting; otherwise fill with pane - if (showShapeless) { - i.setItem(53, shapeless); - } else { - i.setItem(53, stained); - } - - ItemStack enabled = XMaterial.SLIME_BALL.parseItem(); - ItemMeta enabledm = enabled.getItemMeta(); - enabledm.setDisplayName(ChatColor.GREEN + "Enabled"); - enabled.setItemMeta(enabledm); - - Boolean toggled = getConfig(configname).isBoolean(configname + ".Enabled") - ? getConfig(configname).getBoolean(configname + ".Enabled") - : null; - if (toggled != null && !toggled) { - enabled = XMaterial.SNOWBALL.parseItem(); - enabledm.setDisplayName(ChatColor.RED + "Disabled"); - enabled.setItemMeta(enabledm); - } - i.setItem(52, enabled); - - ItemStack cancel = XMaterial.RED_STAINED_GLASS_PANE.parseItem(); - ItemMeta cancelm = cancel.getItemMeta(); - cancelm.setDisplayName(ChatColor.DARK_RED + "Cancel"); - cancel.setItemMeta(cancelm); - i.setItem(48, cancel); - - ItemStack mainmenu = XMaterial.WHITE_STAINED_GLASS_PANE.parseItem(); - ItemMeta mainmenum = mainmenu.getItemMeta(); - mainmenum.setDisplayName("Main Menu"); - mainmenu.setItemMeta(mainmenum); - i.setItem(49, mainmenu); - - ItemStack update = XMaterial.GREEN_STAINED_GLASS_PANE.parseItem(); - ItemMeta updatem = update.getItemMeta(); - updatem.setDisplayName(ChatColor.DARK_GREEN + "Update"); - update.setItemMeta(updatem); - i.setItem(50, update); - - ItemStack delete = XMaterial.BARRIER.parseItem(); - ItemMeta deletem = delete.getItemMeta(); - deletem.setDisplayName(ChatColor.DARK_RED + "Delete Recipe"); - delete.setItemMeta(deletem); - i.setItem(45, delete); - } - - String chatColor(String st) { - if (st == null) - return null; - if (st.equalsIgnoreCase("false")) - return st; - return ChatColor.translateAlternateColorCodes('&', st); - } - - ItemStack itemIngredients(Ingredient ingredient, String recipename, int slot, OfflinePlayer p) { - if (ingredient.hasIdentifier()) { - Boolean isCustomItem = getRecipeUtil().getRecipeFromKey(ingredient.getIdentifier()) == null ? true : false; - ItemStack item = isCustomItem ? getRecipeUtil().getResultFromKey(ingredient.getIdentifier()) - : getRecipeUtil().getRecipeFromKey(ingredient.getIdentifier()).getResult(); - - if (item == null) { - logError("Failed to set item ingredients! Could not find itemstack for ingredient slot " - + ingredient.getSlot() + ". isCustomItem: " + isCustomItem, recipename); - return null; - } - - ItemStack finalItem = new ItemStack(item); - ItemMeta itemM = finalItem.getItemMeta(); - if (!isCustomItem && finalItem.hasItemMeta() && itemM.hasLore() && hasPlaceholder()) { - itemM.setLore(PlaceholderAPI.setPlaceholders(p, itemM.getLore())); - finalItem.setItemMeta(itemM); - } - return finalItem; - } - return null; - } - - @EventHandler - public void onClick(InventoryClickEvent e) { - - if (!(e.getWhoClicked() instanceof Player) || e.getClickedInventory() == null - || e.getClickedInventory().getType() == InventoryType.PLAYER) - return; - - Player p = (Player) e.getWhoClicked(); - String inventoryTitle = CompatibilityUtil.getTitle(e); - - if (e.getInventory() != null && inventoryTitle != null && inventoryTitle.contains("VIEWING: ")) { - e.setCancelled(true); - - if (e.getCurrentItem() == null || e.getCurrentItem().getType() == Material.AIR || e.getRawSlot() == 24) - return; - - if (getRecipeUtil().getRecipeFromResult(e.getCurrentItem()) != null) { - String name = getRecipeUtil().getRecipeFromResult(e.getCurrentItem()).getName(); - Inventory edit = Bukkit.getServer().createInventory(null, 54, - ChatColor.translateAlternateColorCodes('&', "&cVIEWING: " + name)); - setItems(false, true, edit, name, null, e.getCurrentItem(), p, null); - Main.getInstance().saveInventory.put(p.getUniqueId(), e.getInventory()); - p.openInventory(edit); - } - - if (e.getRawSlot() == 48 || e.getRawSlot() == 49 || e.getRawSlot() == 50) { - if (Main.getInstance().saveInventory.containsKey(p.getUniqueId())) { - p.openInventory(Main.getInstance().saveInventory.get(p.getUniqueId())); - Main.getInstance().saveInventory.remove(p.getUniqueId()); - } else { - Main.getInstance().typeGUI.open(p); - } - } - return; - } - - if (e.getInventory() != null && inventoryTitle != null && inventoryTitle.contains("EDITING: ")) { - - String[] split = inventoryTitle.split("EDITING: "); - String recipeName = split.length == 2 ? split[1] : null; - - // Type-aware click allowance - Recipe r = Main.getInstance().getRecipeUtil().getRecipe(recipeName); - - ItemMeta converterItem = e.getInventory().getItem(17).getItemMeta(); - String converterLore = converterItem.hasLore() ? converterItem.getLore().get(0) : "SHAPED"; - - RecipeType creationType = RecipeType.valueOf(ChatColor.stripColor(converterLore)); - RecipeType type = (r != null && r.getType() != null) ? r.getType() : creationType; - boolean showShapeless = (type == RecipeType.SHAPED || type == RecipeType.SHAPELESS); - boolean isCooking = (type == RecipeType.FURNACE || type == RecipeType.BLASTFURNACE - || type == RecipeType.SMOKER || type == RecipeType.CAMPFIRE); - - // Control buttons (always handled below) - HashSet controls = new HashSet<>(); - int[] craftingSlots = { 11, 12, 13, 20, 21, 22, 29, 30, 31, 24 }; - int[] cookingSlots = { 11, 29, 24 }; - int[] otherSlots = { 20, 22, 24 }; - - int[] controlSlots = craftingSlots; - - if (!showShapeless) { - controlSlots = otherSlots; - if (isCooking) - controlSlots = cookingSlots; - } - - for (int s : controlSlots) - controls.add(s); - - if (!controls.contains(e.getRawSlot())) { - e.setCancelled(true); - } - - if (e.getRawSlot() == 7) - return; // effects button - if (e.getRawSlot() == 17) - return; // enchants button - - if (recipeName == null || recipeName.isEmpty() || recipeName.equals(" ") || recipeName.equals("null")) { - ItemMeta nameItem = e.getInventory().getItem(0).getItemMeta(); - String newTitle = nameItem.hasLore() ? nameItem.getLore().get(0) : null; - if (recipeName == null || !recipeName.equals(newTitle)) - e.getView().setTitle(ChatColor.GREEN + "EDITING: " + newTitle); - inventoryinstance.put(p.getUniqueId(), p.getOpenInventory().getTopInventory()); - } - if (e.getRawSlot() == 35) { - // amount button - handleStringMessage(p, "Amount", e); - } - - ItemStack toggle = e.getCurrentItem(); - if (toggle == null || toggle.getItemMeta() == null) - return; - ItemMeta togglem = toggle.getItemMeta(); - - if (e.getRawSlot() == 44) { - // item lore - ItemStack item = e.getInventory().getItem(44); - if (item.hasItemMeta() && item.getItemMeta().hasLore()) { - lore = item.getItemMeta().getLore(); - } - inventoryinstance.put(p.getUniqueId(), p.getOpenInventory().getTopInventory()); - editmeta.put(p.getUniqueId(), "Lore"); - p.closeInventory(); - sendLoremsg(p); - } - - if (e.getRawSlot() == 48) { - // cancel button - p.sendMessage(ChatColor.RED + "You have successfully cancelled any changes to the recipe '" + recipeName - + "'"); - p.closeInventory(); - } - - if (e.getRawSlot() == 49) { - // main menu button - Main.getInstance().typeGUI.open(p); - } - - if (e.getRawSlot() == 50) { - // update button - saveChanges.saveRecipe(e.getClickedInventory(), p, recipeName); - } - - if (e.getRawSlot() == 52) { - // recipe toggle enable/disable - if (togglem.getDisplayName().contains("Enabled")) { - togglem.setDisplayName(ChatColor.RED + "Disabled"); - toggle.setItemMeta(togglem); - p.getOpenInventory().getTopInventory().setItem(52, toggle); - e.getCurrentItem().setType(XMaterial.SNOWBALL.parseMaterial()); - return; - } - if (togglem.getDisplayName().contains("Disabled")) { - togglem.setDisplayName(ChatColor.GREEN + "Enabled"); - toggle.setItemMeta(togglem); - p.getOpenInventory().getTopInventory().setItem(52, toggle); - e.getCurrentItem().setType(XMaterial.SLIME_BALL.parseMaterial()); - return; - } - } - - if (e.getRawSlot() == 8) { - toggleBooleanDisplay(p, e, toggle, 8, "Placeable: "); - return; - } - - if (e.getRawSlot() == 53 && showShapeless) { - toggleBooleanDisplay(p, e, toggle, 53, "Shapeless: "); - return; - } - - if (e.getRawSlot() == 18) { - toggleBooleanDisplay(p, e, toggle, 18, "Exact-Choice: "); - return; - } - - if (e.getRawSlot() == 9) - handleStringMessage(p, "Identifier", e); - if (e.getRawSlot() == 26) - handleStringMessage(p, "Permission", e); - if (e.getRawSlot() == 0) - handleStringMessage(p, "Name", e); - - if (e.getRawSlot() == 45) { - if (recipeName == null || recipeName.isEmpty() || recipeName.equals(" ") || recipeName.equals("null")) - return; - - if (ChatColor.stripColor(e.getClickedInventory().getItem(45).getItemMeta().getDisplayName()) - .equals("Confirm Recipe Deletion")) { - - File recipeFile = new File(Main.getInstance().getDataFolder(), "recipes/" + recipeName + ".yml"); - - if (recipeFile.exists()) { - if (recipeFile.delete()) { - p.sendMessage(ChatColor.GREEN + "Recipe '" + recipeName + "' was successfully deleted."); - } else { - p.sendMessage(ChatColor.RED + "Failed to delete recipe '" + recipeName + "'."); - } - } - - Main.getInstance().reload(); - Main.getInstance().typeGUI.open(p); - return; - } - - ItemStack confirm = e.getClickedInventory().getItem(45); - ItemMeta confirmm = e.getClickedInventory().getItem(45).getItemMeta(); - confirmm.setDisplayName(ChatColor.DARK_RED + "Confirm Recipe Deletion"); - confirm.setItemMeta(confirmm); - } - } - } - - void toggleBooleanDisplay(Player p, InventoryClickEvent e, ItemStack toggle, int slot, String labelPrefix) { - - ItemMeta meta = toggle.getItemMeta(); - String name = ChatColor.stripColor(meta.getDisplayName()); - - if (name.contains("true")) { - meta.setDisplayName(labelPrefix + ChatColor.RED + "false"); - toggle.setItemMeta(meta); - p.getOpenInventory().getTopInventory().setItem(slot, toggle); - } else if (name.contains("false")) { - meta.setDisplayName(labelPrefix + ChatColor.GREEN + "true"); - toggle.setItemMeta(meta); - p.getOpenInventory().getTopInventory().setItem(slot, toggle); - } - } - - public void sendLoremsg(Player p) { - - int num = 1; - - p.sendMessage(ChatColor.DARK_GRAY + "---------------------------------------------"); - p.sendMessage("Please type your new recipe lore"); - p.sendMessage("Current Lore: "); - p.sendMessage(" "); - - if (lore != null) { - for (String line : lore) { - p.sendMessage(num + ": " + line); - num++; - } - } else { - p.sendMessage("NO LORE"); - } - - p.sendMessage(" "); - p.sendMessage("Type 'LINE#: string' to edit/add a line to your lore."); - p.sendMessage("Type 'LINE#: remove' to remove a line."); - p.sendMessage("Type ONLY the line number for an empty space."); - p.sendMessage("Type " + ChatColor.RED + "CANCEL " + ChatColor.WHITE + "to go back."); - p.sendMessage("Type " + ChatColor.GREEN + "DONE " + ChatColor.WHITE + "when finished."); - p.sendMessage(ChatColor.DARK_GRAY + "---------------------------------------------"); - } - - boolean hasPlaceholder() { - return Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null; - } - - public void handleLore(int slot, String string, Player p, AsyncPlayerChatEvent e) { - - Inventory inv = inventoryinstance.get(p.getUniqueId()); - ItemStack item = inv.getItem(44); - ItemStack result = inv.getItem(24); // result slot updated - - if (!editmeta.containsKey(p.getUniqueId())) - return; - - if (e.getMessage().equalsIgnoreCase("done")) { - - if (!lore.isEmpty() && result != null && item.hasItemMeta()) { - ItemMeta itemm = item.getItemMeta(); - itemm.setLore(lore); - - if (hasPlaceholder()) - itemm.setLore(PlaceholderAPI.setPlaceholders(p, lore)); - - item.setItemMeta(itemm); - - ItemMeta resultm = result.getItemMeta(); - resultm.setLore(lore); - - if (hasPlaceholder()) - resultm.setLore(PlaceholderAPI.setPlaceholders(p, lore)); - - result.setItemMeta(resultm); - } - - lore.clear(); - - Bukkit.getScheduler().runTask(Bukkit.getPluginManager().getPlugin("CustomRecipes"), () -> { - p.openInventory(inv); - }); - - editmeta.remove(p.getUniqueId()); - inventoryinstance.remove(p.getUniqueId()); - return; - } - - if (e.getMessage().equalsIgnoreCase("cancel")) { - lore.clear(); - - Bukkit.getScheduler().runTask(Bukkit.getPluginManager().getPlugin("CustomRecipes"), () -> { - p.openInventory(inv); - }); - - editmeta.remove(p.getUniqueId()); - inventoryinstance.remove(p.getUniqueId()); - return; - } - - String[] spl = e.getMessage().split(": "); - - if (spl == null || !isInt(spl[0]) || Integer.parseInt(spl[0]) <= 0) { - p.sendMessage(ChatColor.RED + "Wrong format! You must first define the line number!"); - return; - } - - if (lore.isEmpty() || lore.size() < Integer.parseInt(spl[0])) { - - if (spl.length < 2) { - lore.add(" "); - sendLoremsg(p); - return; - } - - lore.add(ChatColor.translateAlternateColorCodes('&', spl[1])); - sendLoremsg(p); - return; - } - - if (spl.length < 2) { - lore.set(Integer.parseInt(spl[0]) - 1, " "); - sendLoremsg(p); - - } else if (spl[1].equals("remove")) { - lore.remove(Integer.parseInt(spl[0]) - 1); - sendLoremsg(p); - - } else { - lore.set(Integer.parseInt(spl[0]) - 1, ChatColor.translateAlternateColorCodes('&', spl[1])); - sendLoremsg(p); - } - } - - @EventHandler - public void onClose(PlayerQuitEvent e) { - UUID uuid = e.getPlayer().getUniqueId(); - - if (Main.getInstance().recipeBook.contains(uuid)) - Main.getInstance().recipeBook.remove(uuid); - - if (Main.getInstance().saveInventory.containsKey(uuid)) - Main.getInstance().saveInventory.remove(uuid); - - if (editmeta.containsKey(uuid)) - editmeta.remove(uuid); - - if (inventoryinstance.containsKey(uuid)) - inventoryinstance.remove(uuid); - - if (getnewid.containsKey(uuid)) - getnewid.remove(uuid); - } - - @EventHandler - public void onChat(AsyncPlayerChatEvent e) { - - Player p = e.getPlayer(); - - if (!editmeta.containsKey(p.getUniqueId()) || editmeta.get(p.getUniqueId()) == null) - return; - - e.setCancelled(true); - - if (editmeta.get(p.getUniqueId()).equals("Lore")) { - handleLore(44, "Lore", p, e); - return; - } - - if (editmeta.get(p.getUniqueId()).equals("identifier")) { - handleString(9, "Identifier", p, e); - return; - } - - if (editmeta.get(p.getUniqueId()).equals("permission")) { - handleString(26, "Permission", p, e); - return; - } - - if (editmeta.get(p.getUniqueId()).equals("name")) { - handleString(0, "Name", p, e); - return; - } - - if (editmeta.get(p.getUniqueId()).equals("amount")) { - handleString(35, "Amount", p, e); - return; - } - } - - public void handleStringMessage(Player p, String s, InventoryClickEvent e) { - inventoryinstance.put(p.getUniqueId(), p.getOpenInventory().getTopInventory()); - editmeta.put(p.getUniqueId(), s.toLowerCase()); - p.closeInventory(); - - List getid = null; - String identifier = null; - if (e.getCurrentItem().getItemMeta().hasLore()) { - getid = e.getCurrentItem().getItemMeta().getLore(); - identifier = getid.get(0); - } else { - String[] split = e.getCurrentItem().getItemMeta().getDisplayName().split(": "); - String name = split.length > 1 ? split[1] : "null"; - identifier = name; - } - - p.sendMessage(ChatColor.DARK_GRAY + "---------------------------------------------"); - p.sendMessage("Please type your new recipe " + s.toLowerCase()); - p.sendMessage("Current " + s + ": " + ChatColor.translateAlternateColorCodes('&', identifier)); - p.sendMessage("Type " + ChatColor.RED + "CANCEL " + ChatColor.WHITE + "to go back."); - p.sendMessage("Type " + ChatColor.GREEN + "DONE " + ChatColor.WHITE + "when finished."); - p.sendMessage(ChatColor.DARK_GRAY + "---------------------------------------------"); - } - - public boolean isInt(String text) { - try { - Integer.parseInt(text); - return true; - } catch (NumberFormatException e) { - return false; - } - } - - private void handleString(int slot, String string, Player p, AsyncPlayerChatEvent e) { - - Inventory inv = inventoryinstance.get(p.getUniqueId()); - ItemStack id = inv.getItem(slot); - ItemMeta idm = id.getItemMeta(); - - if (!e.getMessage().equalsIgnoreCase("done") && !e.getMessage().equalsIgnoreCase("cancel")) { - p.sendMessage(" "); - p.sendMessage(ChatColor.DARK_GRAY + "---------------------------------------------"); - p.sendMessage(ChatColor.GRAY + ChatColor.translateAlternateColorCodes('&', e.getMessage()) + ChatColor.WHITE - + " has been selected as the new " + string.toLowerCase() + "."); - p.sendMessage("Type " + ChatColor.GREEN + "DONE " + ChatColor.WHITE + "to confirm your selection!"); - p.sendMessage("Type " + ChatColor.RED + "CANCEL " + ChatColor.WHITE + "to cancel your selection!"); - p.sendMessage(ChatColor.GRAY + "Or retype selection for a different " + string.toLowerCase()); - p.sendMessage(ChatColor.DARK_GRAY + "---------------------------------------------"); - - getnewid.put(p.getUniqueId(), e.getMessage()); - } - - if (getnewid.containsKey(p.getUniqueId()) && e.getMessage().equalsIgnoreCase("done")) { - - String newidf = getnewid.get(p.getUniqueId()); - List loreList = new ArrayList<>(); - - if (!string.equals("Amount")) { - loreList.add(ChatColor.GRAY + ChatColor.translateAlternateColorCodes('&', newidf)); - idm.setLore(loreList); - id.setItemMeta(idm); - inv.setItem(slot, id); - } - - if (string.equals("Amount")) { - if (!isInt(newidf)) { - p.sendMessage(" "); - p.sendMessage(ChatColor.RED + "Value must be in number format! Please try again."); - return; - } - ItemStack item = inv.getItem(35); - ItemMeta itemm = item.getItemMeta(); - itemm.setDisplayName(ChatColor.WHITE + "Amount: " + ChatColor.GRAY + newidf); - item.setItemMeta(itemm); - inv.setItem(35, item); - - if (inv.getItem(24) != null) - inv.getItem(24).setAmount(Integer.valueOf(newidf)); // result slot updated - } - - Bukkit.getScheduler().runTask(Bukkit.getPluginManager().getPlugin("CustomRecipes"), () -> { - p.openInventory(inv); - }); - - inventoryinstance.remove(p.getUniqueId()); - editmeta.remove(p.getUniqueId()); - getnewid.remove(p.getUniqueId()); - return; - } - - if (!getnewid.containsKey(p.getUniqueId()) || e.getMessage().equalsIgnoreCase("cancel")) { - editmeta.remove(p.getUniqueId()); - Bukkit.getScheduler().runTask(Bukkit.getPluginManager().getPlugin("CustomRecipes"), () -> { - p.openInventory(inv); - }); - - inventoryinstance.remove(p.getUniqueId()); - } - } - - // Legacy helper left as-is for compatibility with other code paths - public boolean hasSomething(Inventory i) { - // updated crafting grid + result slot - if ((i.getItem(11) != null || i.getItem(12) != null || i.getItem(13) != null || i.getItem(20) != null - || i.getItem(21) != null || i.getItem(22) != null || i.getItem(29) != null || i.getItem(30) != null - || i.getItem(31) != null) && i.getItem(24) != null) { - return true; - } - return false; - } - - private void logError(String st, String recipe) { - Logger.getLogger("Minecraft").log(Level.WARNING, - "[DEBUG][" + Main.getInstance().getName() + "][" + recipe + "] " + st); - } -} \ No newline at end of file diff --git a/src/me/mehboss/gui/RecipeSaver.java b/src/me/mehboss/gui/RecipeSaver.java index 21ec199..c2b18be 100644 --- a/src/me/mehboss/gui/RecipeSaver.java +++ b/src/me/mehboss/gui/RecipeSaver.java @@ -10,10 +10,11 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; import org.bukkit.ChatColor; +import org.bukkit.Color; import org.bukkit.Material; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; @@ -22,189 +23,491 @@ import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.PotionMeta; +import org.bukkit.potion.PotionData; +import org.bukkit.potion.PotionEffect; + +import com.cryptomorin.xseries.XMaterial; +import com.cryptomorin.xseries.XPotion; import io.github.bananapuncher714.nbteditor.NBTEditor; +import me.mehboss.gui.framework.GuiButton; +import me.mehboss.gui.framework.GuiButton.GuiStringButton; +import me.mehboss.gui.framework.GuiButton.GuiToggleButton; +import me.mehboss.gui.framework.GuiView; +import me.mehboss.gui.framework.RecipeLayout; import me.mehboss.recipe.Main; +import me.mehboss.utils.CompatibilityUtil; +import me.mehboss.utils.RecipeUtil.Recipe; import me.mehboss.utils.RecipeUtil.Recipe.RecipeType; public class RecipeSaver { - // GUI slot constants - private static final int SLOT_IDENTIFIER = 9; - private static final int SLOT_PLACEABLE_TOGGLE = 8; - private static final int SLOT_RESULT = 24; - private static final int SLOT_PERMISSION = 26; - private static final int SLOT_ENABLED_TOGGLE = 52; - private static final int SLOT_SHAPELESS_TOGGLE = 53; - private static final int SLOT_EXACTCHOICE_TOGGLE = 18; - private static final int SLOT_CONVERTER = 17; - - // Crafting grid slots (keep the same as before) - private static final int[] CRAFT_SLOTS = { 11, 12, 13, 20, 21, 22, 29, 30, 31 }; - private static final int[] COOKING_SLOTS = { 11, 29 }; - private static final int[] OTHER_SLOTS = { 20, 22 }; - private static int[] CONTROL_SLOTS = CRAFT_SLOTS; - - // Save the recipe map to the configuration file - public void saveRecipe(Inventory inv, Player p, String coloredName) { - - String recipeName = ChatColor.stripColor(coloredName); - File recipesFolder = new File(Main.getInstance().getDataFolder(), "recipes"); - if (!recipesFolder.exists()) { - recipesFolder.mkdirs(); - } + /** + * Saves the contents of a recipe GUI to its YAML file. + * + * @param view The active GuiView (editing view) + * @param player The player who clicked "Update" + * @param recipe The logical recipe being edited/created + */ + public void saveRecipe(GuiView view, Player player, Recipe recipe) { + + Inventory inv = view.getInventory(); + RecipeType type = recipe.getType(); - if (recipeName == null || recipeName.isEmpty() || recipeName.equals("null") || recipeName.equals(" ")) + if (type == null) { + player.sendMessage(ChatColor.RED + "Recipe has no type set; cannot save."); return; + } - String rawConverter = readLoreTag(inv); - RecipeType type = rawConverter.equals("none") ? RecipeType.SHAPED : RecipeType.valueOf(rawConverter); - boolean showShapeless = (type == RecipeType.SHAPED || type == RecipeType.SHAPELESS); - boolean isCooking = (type == RecipeType.FURNACE || type == RecipeType.BLASTFURNACE || type == RecipeType.SMOKER - || type == RecipeType.CAMPFIRE); + RecipeLayout layout = RecipeLayout.forType(type); + int[] ingredientSlots = layout.getIngredientSlots(); + int resultSlot = layout.getResultSlot(); - if (!showShapeless) { - CONTROL_SLOTS = OTHER_SLOTS; - if (isCooking) - CONTROL_SLOTS = COOKING_SLOTS; + String recipeName = recipe.getName(); + if (recipeName == null || recipeName.trim().isEmpty()) { + player.sendMessage(ChatColor.RED + "Recipe name is empty; cannot save."); + return; } + // Ensure there is at least one ingredient and a result boolean matrixEmpty = true; - boolean slotEmpty = inv.getItem(SLOT_RESULT) == null || inv.getItem(SLOT_RESULT).getType() == Material.AIR; - for (int slot : CONTROL_SLOTS) { - if (inv.getItem(slot) != null && inv.getItem(slot).getType() != Material.AIR) { + for (int slot : ingredientSlots) { + if (!isEmpty(inv, slot)) { matrixEmpty = false; break; } } - if (matrixEmpty || slotEmpty) { + if (matrixEmpty || isEmpty(inv, resultSlot)) { + player.sendMessage(ChatColor.RED + "Recipe matrix or result is empty; nothing to save."); return; } + // Prepare recipes folder + File recipesFolder = new File(Main.getInstance().getDataFolder(), "recipes"); + if (!recipesFolder.exists()) { + recipesFolder.mkdirs(); + } + File recipeFile = new File(recipesFolder, recipeName + ".yml"); FileConfiguration recipeConfig = YamlConfiguration.loadConfiguration(recipeFile); - Map recipeData = convertToRecipeConfig(inv, recipeName); + Map recipeData = convertToRecipeConfig(view, inv, recipe, type, layout); recipeConfig.set(recipeName, recipeData); try { recipeConfig.save(recipeFile); - p.sendMessage(ChatColor.GREEN + "Recipe saved successfully to " + recipeFile.getName() + "!"); + player.sendMessage(ChatColor.GREEN + "Recipe saved successfully to " + recipeFile.getName() + "!"); } catch (IOException e) { e.printStackTrace(); - p.sendMessage(ChatColor.RED + "Failed to save the recipe to " + recipeFile.getName() + "."); + player.sendMessage(ChatColor.RED + "Failed to save the recipe to " + recipeFile.getName() + "."); } - Main.getInstance().reload(); - p.closeInventory(); + Main.getInstance().recipeManager.addRecipes(recipeName); + player.closeInventory(); } - public Map convertToRecipeConfig(Inventory inventory, String recipeName) { - // Use LinkedHashMap so YAML preserves our field order (matching your example) + private Map convertToRecipeConfig(GuiView view, Inventory inventory, Recipe recipe, RecipeType type, + RecipeLayout layout) { + LinkedHashMap cfg = new LinkedHashMap<>(); + int[] ingredientSlots = layout.getIngredientSlots(); + int resultSlot = layout.getResultSlot(); + // ====== Result item ====== - ItemStack resultItem = safeGet(inventory, SLOT_RESULT); + ItemStack resultItem = safeGet(inventory, resultSlot); int resultAmount = (resultItem != null && resultItem.getAmount() > 0) ? resultItem.getAmount() : 1; - // ====== Read GUI toggles and text ====== - AtomicBoolean resultHasID = new AtomicBoolean(false); - boolean enabled = readEnabledToggle(inventory); - boolean shapeless = readBooleanToggle(inventory, SLOT_SHAPELESS_TOGGLE); - boolean exactchoice = readBooleanToggle(inventory, SLOT_EXACTCHOICE_TOGGLE); - boolean placeable = readBooleanToggle(inventory, SLOT_PLACEABLE_TOGGLE); - String identifier = readIdentifier(inventory, resultItem, recipeName); - String permission = readPermission(inventory, identifier); - String displayNameColored = readNameColored(inventory); - - String rawConverterType = readLoreTag(inventory); - String converterType = rawConverterType.equals("SHAPED") || rawConverterType.equals("SHAPELESS") ? "none" - : rawConverterType; + // ====== High-level flags ====== + boolean enabled = readEnabledToggle(view); + boolean shapeless = readBooleanToggle(view, "Shapeless", false); + boolean exactChoice = readBooleanToggle(view, "Exact-Choice", true); + boolean placeable = readBooleanToggle(view, "Placeable", false); + + // ====== Textual fields ====== + String identifier = readIdentifier(view, recipe.getName()); + String permission = readStringField(view, "Permission", "none"); + String group = readStringField(view, "Group", "none"); + if ("none".equalsIgnoreCase(group)) + group = ""; + + String displayNameColored = readNameColored(resultItem); + // ====== Effects (currently unused, keep as empty list) ====== + List effects = new ArrayList<>(); // or from button "Effects" later + + // ====== Enchantments & lore ====== List enchantList = readEnchantList(resultItem); - Map letters = setItemLetters(inventory); - List itemCrafting = generateItemCrafting(letters, inventory); + AtomicBoolean resultHasID = new AtomicBoolean(false); + String material = getItemValue(resultItem, identifier, resultHasID); List lore = getItemLoreList(resultItem); if (lore == null) lore = new ArrayList<>(); - LinkedHashMap ingredients = buildIngredientsFromLetters(inventory, letters); + // ====== Ingredient mapping / pattern ====== + Map letters = setItemLetters(inventory, ingredientSlots); + List itemCrafting = generateItemCrafting(letters, inventory, ingredientSlots); + LinkedHashMap ingredients = buildIngredientsFromLetters(inventory, letters, ingredientSlots); + + String converterType = (type == RecipeType.SHAPED || type == RecipeType.SHAPELESS) ? "none" : type.toString(); + + boolean isCooking = type == RecipeType.FURNACE || type == RecipeType.BLASTFURNACE || type == RecipeType.SMOKER + || type == RecipeType.CAMPFIRE; + boolean hasRepairCost = type == RecipeType.GRINDSTONE || type == RecipeType.ANVIL; + boolean isBrewing = type == RecipeType.BREWING_STAND; + boolean isCrafting = type == RecipeType.SHAPED || type == RecipeType.SHAPELESS; + // ====== Base fields (mostly unchanged from original) ====== cfg.put("Enabled", enabled); - cfg.put("Shapeless", shapeless); - cfg.put("Cooldown", 60); - cfg.put("Item", getItemValue(resultItem, identifier, resultHasID)); - cfg.put("Item-Damage", "none"); - cfg.put("Amount", resultAmount); + if (isCrafting) + cfg.put("Shapeless", shapeless); + if (isCrafting || type == RecipeType.STONECUTTER) + cfg.put("Group", group); + cfg.put("Cooldown", -1); cfg.put("Placeable", placeable); - cfg.put("Ignore-Data", false); - cfg.put("Ignore-Model-Data", false); - cfg.put("Multi-Resulted", false); - cfg.put("Exact-Choice", exactchoice); - cfg.put("Custom-Tagged", false); - cfg.put("Durability", resultItem.getDurability()); - + cfg.put("Exact-Choice", exactChoice); cfg.put("Identifier", identifier); + + // Converter: use recipe type; crafting types use "none" like before cfg.put("Converter", converterType); - cfg.put("Permission", permission); + // ---- Cooking: Cook-Time & Experience ---- + if (isCooking) { + Integer cookTime = readIntField(view, "Cook-Time"); + if (cookTime != null) { + cfg.put("Cook-Time", cookTime); + } + } + + if (isCooking || type == RecipeType.GRINDSTONE) { + Double exp = readDoubleField(view, "Experience"); + if (exp != null) { + cfg.put("Experience", exp); + } + } + + // ---- Workstation: Repair-Cost ---- + if (hasRepairCost) { + Integer repairCost = readIntField(view, "Repair-Cost"); + if (repairCost != null) { + cfg.put("Repair-Cost", repairCost); + } + } + + // ---- Brewing: Required-Items, Fuel-Set, Fuel-Charge ---- + if (isBrewing) { + Integer fuelSet = readIntField(view, "Fuel-Set"); + if (fuelSet != null) { + cfg.put("FuelSet", fuelSet); + } + + Integer fuelCharge = readIntField(view, "Fuel-Charge"); + if (fuelCharge != null) { + cfg.put("FuelCharge", fuelCharge); + } + + boolean requiresItems = readBooleanToggle(view, "Required-Items", false); + cfg.put("Required-Items", requiresItems); + } + + cfg.put("Permission", permission); cfg.put("Auto-Discover-Recipe", true); cfg.put("Book-Category", "MISC"); - if (resultHasID.get()) { - cfg.put("Name", "none"); - cfg.put("Lore", null); - - } else { - if (displayNameColored != null && !displayNameColored.isEmpty()) { - cfg.put("Name", displayNameColored); + boolean isPotion = material == XMaterial.POTION.get().toString() + || material == XMaterial.SPLASH_POTION.get().toString() + || material == XMaterial.LINGERING_POTION.get().toString(); + + // handles "result" configuration section + Map result = new LinkedHashMap<>(); + if (!isPotion && !resultHasID.get()) { + result.put("Item", material); + result.put("Item-Damage", "none"); + result.put("Durability", (resultItem != null) ? resultItem.getDurability() : 0); + result.put("Amount", resultAmount); + + if (resultHasID.get()) { + result.put("Name", "none"); + result.put("Lore", new ArrayList<>()); } else { - cfg.put("Name", "none"); + result.put("Name", + (displayNameColored != null && !displayNameColored.isEmpty()) ? displayNameColored : "none"); + result.put("Lore", lore); } - cfg.put("Lore", lore); + result.put("Hide-Enchants", true); + result.put("Enchantments", enchantList); + result.put("Custom-Tagged", false); + result.put("Custom-Model-Data", "none"); + result.put("Item-Flags", new ArrayList<>()); + result.put("Attribute", new ArrayList<>()); + result.put("Custom-Tags", new ArrayList<>()); + } else if (isPotion) { + savePotionToConfig(resultItem, result); } + // adds the section back to parent + cfg.put("Result", result); + + if (!isBrewing) { + cfg.put("Effects", effects); - cfg.put("Effects", new ArrayList<>()); - cfg.put("Hide-Enchants", true); - cfg.put("Enchantments", enchantList); + Map commands = new LinkedHashMap<>(); + commands.put("Give-Item", true); + commands.put("Run-Commands", new ArrayList<>()); + cfg.put("Commands", commands); + } cfg.put("ItemCrafting", itemCrafting); cfg.put("Ingredients", ingredients); - cfg.put("Custom-Tags", new ArrayList<>()); - cfg.put("Item-Flags", new ArrayList<>()); - cfg.put("Attribute", new ArrayList<>()); - cfg.put("Custom-Model-Data", "none"); + cfg.put("ItemsLeftover", new ArrayList<>()); + + Map flags = new LinkedHashMap<>(); + flags.put("Ignore-Data", false); + flags.put("Ignore-Model-Data", false); + flags.put("Ignore-Name", false); + cfg.put("Flags", flags); + + cfg.put("Use-Conditions", false); + cfg.put("Conditions_All", new ArrayList<>()); cfg.put("Disabled-Worlds", new ArrayList<>()); return cfg; } - private String getItemValue(ItemStack item, String id, AtomicBoolean hasID) { - if (item == null || item.getType() == Material.AIR) - return "AIR"; // fallback for empty slots + public void savePotionToConfig(ItemStack item, Map configValues) { + Map cfg = configValues; - // Try custom key from RecipeUtil (for plugin-based custom items) - String key = Main.getInstance().getRecipeUtil().getKeyFromResult(item); - if (key != null && !key.isEmpty() && !key.equals(id)) { - hasID.set(true); - return key; + // --- Material --- + cfg.put("Item", item.getType().name()); + + if (!(item.getItemMeta() instanceof PotionMeta)) { + return; } - // Fallback to Material name - return item.getType().toString(); + PotionMeta meta = (PotionMeta) item.getItemMeta(); + + // --- Name --- + if (meta.hasDisplayName()) + cfg.put("Name", meta.getDisplayName()); + + // --- Color --- + if (meta.hasColor()) { + Color c = meta.getColor(); + cfg.put("Color", c.getRed() + "," + c.getGreen() + "," + c.getBlue()); + } // Try to detect vanilla vs custom + boolean vanilla = meta.hasCustomEffects() && meta.getBasePotionData() != null; + + // ===================================================== + // Case 1: VANILLA POTION (BasePotionData) + // ===================================================== + if (meta.getBasePotionData() != null && vanilla) { + PotionData pd = meta.getBasePotionData(); + + // XPotion reverse lookup + XPotion xp = XPotion.matchXPotion(pd.getType()); + + cfg.put("PotionType", xp.getPotionType().name()); + cfg.put("Amplifier", pd.isUpgraded() ? 1 : 0); + cfg.put("Duration", pd.isExtended() ? 3600 : 1800); // approx + + return; + } + + // ===================================================== + // Case 2: CUSTOM EFFECTS + // ===================================================== + if (meta.getCustomEffects().isEmpty()) { + return; + } + + List> list = new ArrayList<>(); + + for (PotionEffect eff : meta.getCustomEffects()) { + Map m = new LinkedHashMap<>(); + m.put("Type", XPotion.of(eff.getType()).get().getName()); + m.put("Duration", eff.getDuration() / 20); + m.put("Amplifier", eff.getAmplifier()); + list.add(m); + } + + cfg.put("Effects", list); + cfg.put("UseVanillaType", vanilla); + cfg.put("Amount", item.getAmount()); } - private LinkedHashMap buildIngredientsFromLetters(Inventory inv, Map letters) { - LinkedHashMap out = new LinkedHashMap<>(); + private GuiStringButton getStringButton(GuiView view, String fieldName) { + Inventory inv = view.getInventory(); + int size = inv.getSize(); + for (int slot = 0; slot < size; slot++) { + GuiButton b = view.getButton(slot); + GuiStringButton sb = b instanceof GuiStringButton ? (GuiStringButton) b : null; + if (sb != null && sb.getFieldName().equalsIgnoreCase(fieldName)) { + return sb; + } + } + return null; + } + + private GuiToggleButton getToggleButton(GuiView view, String fieldName) { + Inventory inv = view.getInventory(); + int size = inv.getSize(); + for (int slot = 0; slot < size; slot++) { + GuiButton b = view.getButton(slot); + GuiToggleButton tb = b instanceof GuiToggleButton ? (GuiToggleButton) b : null; + if (tb != null && tb.getFieldName().equalsIgnoreCase(fieldName)) { + return tb; + } + } + return null; + } + + private boolean readEnabledToggle(GuiView view) { + // Enabled uses field name "Enabled" and a SLIME_BALL/SNOWBALL icon + GuiToggleButton btn = getToggleButton(view, "Enabled"); + if (btn == null) { + return true; // default to enabled + } + ItemStack icon = btn.getIcon(); + if (icon == null || !icon.hasItemMeta()) { + return true; + } + String name = ChatColor.stripColor(icon.getItemMeta().getDisplayName()); + if (name == null) + return true; + String lc = name.toLowerCase(); + if (lc.contains("disabled")) + return false; + if (lc.contains("enabled")) + return true; + return true; + } + + private boolean readBooleanToggle(GuiView view, String fieldName, boolean defaultVal) { + GuiToggleButton btn = getToggleButton(view, fieldName); + if (btn == null) + return defaultVal; + ItemStack icon = btn.getIcon(); + if (icon == null || !icon.hasItemMeta()) + return defaultVal; + String name = ChatColor.stripColor(icon.getItemMeta().getDisplayName()); + if (name == null) + return defaultVal; + String lc = name.toLowerCase(); + if (lc.contains("true")) + return true; + if (lc.contains("false")) + return false; + return defaultVal; + } + + private String readIdentifier(GuiView view, String fallbackName) { + String id = readStringField(view, "Identifier", null); + if (id != null && !id.isEmpty() && !id.equalsIgnoreCase("null") && !id.equalsIgnoreCase("none")) + return id; + return fallbackName; + } + + private String readStringField(GuiView view, String fieldName, String defaultVal) { + GuiStringButton btn = getStringButton(view, fieldName); + if (btn == null) + return defaultVal; + + ItemStack icon = btn.getIcon(); + if (icon == null || !icon.hasItemMeta()) + return defaultVal; + ItemMeta im = icon.getItemMeta(); + + if (im.hasLore() && im.getLore() != null && !im.getLore().isEmpty()) { + return ChatColor.stripColor(im.getLore().get(0)); + } + if (im.hasDisplayName()) { + return ChatColor.stripColor(im.getDisplayName()); + } + return defaultVal; + } + + private Integer readIntField(GuiView view, String fieldName) { + GuiStringButton btn = getStringButton(view, fieldName); + if (btn == null) + return null; + ItemStack icon = btn.getIcon(); + if (icon == null || !icon.hasItemMeta()) + return null; + ItemMeta im = icon.getItemMeta(); + String src = null; + + if (im.hasLore() && im.getLore() != null && !im.getLore().isEmpty()) { + src = ChatColor.stripColor(im.getLore().get(0)); + } else if (im.hasDisplayName()) { + src = ChatColor.stripColor(im.getDisplayName()); + } + + if (src == null) + return null; + + src = src.replaceAll("[^0-9-]", ""); // strip non-digits + if (src.isEmpty()) + return null; + + try { + return Integer.parseInt(src); + } catch (NumberFormatException e) { + return null; + } + } + + private Double readDoubleField(GuiView view, String fieldName) { + GuiStringButton btn = getStringButton(view, fieldName); + if (btn == null) + return null; + ItemStack icon = btn.getIcon(); + if (icon == null || !icon.hasItemMeta()) + return null; + ItemMeta im = icon.getItemMeta(); + String src = null; + + if (im.hasLore() && im.getLore() != null && !im.getLore().isEmpty()) { + src = ChatColor.stripColor(im.getLore().get(0)); + } else if (im.hasDisplayName()) { + src = ChatColor.stripColor(im.getDisplayName()); + } + + if (src == null) + return null; + src = src.replaceAll("[^0-9+\\-\\.]", ""); + if (src.isEmpty()) + return null; + + try { + return Double.parseDouble(src); + } catch (NumberFormatException e) { + return null; + } + } + + private String readNameColored(ItemStack item) { + if (item == null || item.getType() == Material.AIR || item.getItemMeta() == null) + return null; + ItemMeta im = item.getItemMeta(); + String customName = CompatibilityUtil.hasDisplayname(im) ? CompatibilityUtil.getDisplayname(im) : "none"; + String displayName = im.hasDisplayName() ? im.getDisplayName() : "none"; + + return (displayName != null && !"none".equals(displayName)) ? displayName : customName; + } + + private LinkedHashMap buildIngredientsFromLetters(Inventory inv, Map letters, + int[] ingredientSlots) { + LinkedHashMap out = new LinkedHashMap<>(); Set seen = new HashSet<>(); - for (int i = 0; i < CONTROL_SLOTS.length; i++) { - ItemStack stack = inv.getItem(CONTROL_SLOTS[i]); + for (int i = 0; i < ingredientSlots.length; i++) { + int slotIndex = ingredientSlots[i]; + ItemStack stack = inv.getItem(slotIndex); if (stack == null || stack.getType() == Material.AIR) continue; @@ -218,19 +521,19 @@ private LinkedHashMap buildIngredientsFromLetters(Inventory inv, ing.put("Identifier", findIngredientIdentifier(stack)); ing.put("Amount", Math.max(1, stack.getAmount())); ing.put("Name", getIngredientNamePlain(stack)); + ing.put("Custom-Model-Data", "none"); out.put(letter, ing); } return out; } - private Map setItemLetters(Inventory inventory) { - int[] ingredientSlots = CONTROL_SLOTS; + private Map setItemLetters(Inventory inventory, int[] ingredientSlots) { Map itemToLetterMap = new HashMap<>(); Set usedLetters = new HashSet<>(); - for (int i = 0; i < ingredientSlots.length; i++) { - ItemStack current = inventory.getItem(ingredientSlots[i]); + for (int slotIndex : ingredientSlots) { + ItemStack current = inventory.getItem(slotIndex); if (current != null && current.getType() != Material.AIR) { ItemStack matchingItem = null; @@ -263,11 +566,9 @@ private String getUniqueLetter(Set usedLetters) { return "X"; } - public List generateItemCrafting(Map ingredients, Inventory inv) { + public List generateItemCrafting(Map ingredients, Inventory inv, int[] ingredientSlots) { List itemCrafting = new ArrayList<>(Arrays.asList("XXX", "XXX", "XXX")); - int[] ingredientSlots = CONTROL_SLOTS; - // Update the crafting pattern with the ingredient letters for (int i = 0; i < ingredientSlots.length; i++) { int slot = ingredientSlots[i]; ItemStack stack = inv.getItem(slot); @@ -289,80 +590,9 @@ public List generateItemCrafting(Map ingredients, Inv return itemCrafting; } - private boolean readEnabledToggle(Inventory inv) { - ItemStack toggle = safeGet(inv, SLOT_ENABLED_TOGGLE); - if (toggle == null || toggle.getType() == Material.AIR || !toggle.hasItemMeta()) { - return true; // default to enabled - } - String name = ChatColor.stripColor(toggle.getItemMeta().getDisplayName()); - return name != null && name.toLowerCase().contains("enabled") && !name.toLowerCase().contains("disabled"); - } - - private String readLoreTag(Inventory inv) { - ItemStack converterItem = safeGet(inv, SLOT_CONVERTER); - if (converterItem == null || converterItem.getType() == Material.AIR || !converterItem.hasItemMeta() - || !converterItem.getItemMeta().hasLore()) { - return "none"; // default to enabled - } - String name = ChatColor.stripColor(converterItem.getItemMeta().getLore().get(0)); - return name; - } - - private boolean readBooleanToggle(Inventory inv, int slot) { - ItemStack toggle = safeGet(inv, slot); - if (toggle == null || toggle.getType() == Material.AIR || !toggle.hasItemMeta()) { - return false; - } - String name = ChatColor.stripColor(toggle.getItemMeta().getDisplayName()); - if (name != null) { - String lc = name.toLowerCase(); - if (lc.contains("true")) - return true; - if (lc.contains("false")) - return false; - } - return false; - } - - private String readIdentifier(Inventory inv, ItemStack result, String fallbackName) { - // Prefer slot 7 lore first line or display name, STRIPPED - String id = readFirstLoreOrNameStripped(inv, SLOT_IDENTIFIER); - if (id != null && !id.isEmpty() && !"none".equalsIgnoreCase(id)) - return id; - - return UUID.randomUUID().toString(); - } - - private String readPermission(Inventory inv, String identifier) { - String perm = readFirstLoreOrNameStripped(inv, SLOT_PERMISSION); - if (perm == null || perm.isEmpty() || "none".equalsIgnoreCase(perm)) { - perm = "crecipe.recipe." + identifier; - } - return perm; - } - - // KEEP colors for Name - private String readNameColored(Inventory inv) { - ItemStack item = safeGet(inv, SLOT_RESULT); - if (item == null || item.getType() == Material.AIR || item.getItemMeta() == null) - return null; - ItemMeta im = item.getItemMeta(); - return im.hasDisplayName() ? im.getDisplayName() : null; - } - - // STRIP colors helper for IDs / permissions - private String readFirstLoreOrNameStripped(Inventory inv, int slot) { - ItemStack item = safeGet(inv, slot); - if (item == null || item.getType() == Material.AIR || item.getItemMeta() == null) - return null; - ItemMeta im = item.getItemMeta(); - if (im.hasLore() && im.getLore() != null && !im.getLore().isEmpty()) { - return ChatColor.stripColor(im.getLore().get(0)); - } - if (im.hasDisplayName()) { - return ChatColor.stripColor(im.getDisplayName()); - } - return null; + private boolean isEmpty(Inventory inv, int slot) { + ItemStack it = safeGet(inv, slot); + return it == null || it.getType() == Material.AIR; } private ItemStack safeGet(Inventory inv, int slot) { @@ -373,6 +603,21 @@ private ItemStack safeGet(Inventory inv, int slot) { } } + private String getItemValue(ItemStack item, String id, AtomicBoolean hasID) { + if (item == null || item.getType() == Material.AIR) + return "AIR"; // fallback for empty slots + + // Try custom key from RecipeUtil (for plugin-based custom items) + String key = Main.getInstance().getRecipeUtil().getKeyFromResult(item); + if (key != null && !key.isEmpty() && !key.equals(id)) { + hasID.set(true); + return key; + } + + // Fallback to Material name + return item.getType().toString(); + } + // KEEP colors: result item lore list (or null) public List getItemLoreList(ItemStack itemStack) { if (itemStack != null && itemStack.hasItemMeta()) { @@ -384,13 +629,6 @@ public List getItemLoreList(ItemStack itemStack) { return null; } - // Result display name (may include colors) - public String getItemName(ItemStack itemStack) { - return (itemStack != null && itemStack.hasItemMeta() && itemStack.getItemMeta().hasDisplayName()) - ? itemStack.getItemMeta().getDisplayName() - : "none"; - } - // Ingredient identifier (recipe ref or NBT), default "none" private String findIngredientIdentifier(ItemStack item) { if (item == null) @@ -409,10 +647,10 @@ private String getIngredientNamePlain(ItemStack item) { if (item == null || !item.hasItemMeta()) return "none"; ItemMeta im = item.getItemMeta(); - if (im.hasDisplayName()) { - return ChatColor.stripColor(im.getDisplayName()); - } - return "none"; + String customName = CompatibilityUtil.hasDisplayname(im) ? CompatibilityUtil.getDisplayname(im) : "none"; + String displayName = im.hasDisplayName() ? im.getDisplayName() : "none"; + + return (displayName != null && !"none".equals(displayName)) ? displayName : customName; } public String getCustomIdentifier(ItemStack itemStack) { diff --git a/src/me/mehboss/gui/RecipeTypeGUI.java b/src/me/mehboss/gui/RecipeTypeGUI.java index 36ab814..1612318 100644 --- a/src/me/mehboss/gui/RecipeTypeGUI.java +++ b/src/me/mehboss/gui/RecipeTypeGUI.java @@ -107,14 +107,12 @@ public void onClick(InventoryClickEvent e) { Player p = (Player) e.getWhoClicked(); String name = ChatColor.stripColor(e.getCurrentItem().getItemMeta().getDisplayName()); - if (name == null) + RecipeType type = name == null || name.split(" ").length < 1 ? null + : RecipeType.fromString(name.split(" ")[0].toUpperCase()); + if (name == null || type == null) return; - try { - RecipeType type = RecipeType.valueOf(name.split(" ")[0].toUpperCase()); - BookGUI.openType(p, type); // send to BookGUI to show recipes of this type - } catch (Exception ignored) { - } + BookGUI.openType(p, type); // send to BookGUI to show recipes of this type } BookGUI BookGUI = Main.getInstance().recipes; diff --git a/src/me/mehboss/gui/RecipeViewBuilder.java b/src/me/mehboss/gui/RecipeViewBuilder.java new file mode 100644 index 0000000..f35c61d --- /dev/null +++ b/src/me/mehboss/gui/RecipeViewBuilder.java @@ -0,0 +1,523 @@ +package me.mehboss.gui; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import com.cryptomorin.xseries.XMaterial; + +import me.mehboss.gui.framework.GuiButton; +import me.mehboss.gui.framework.GuiButton.GuiStringButton; +import me.mehboss.gui.framework.GuiButton.GuiLoreButton; +import me.mehboss.gui.framework.GuiButton.GuiToggleButton; +import me.mehboss.gui.framework.GuiView; +import me.mehboss.gui.framework.RecipeItemFactory; +import me.mehboss.gui.framework.RecipeLayout; +import me.mehboss.recipe.Main; +import me.mehboss.utils.RecipeUtil.Ingredient; +import me.mehboss.utils.RecipeUtil.Recipe; +import me.mehboss.utils.RecipeUtil.Recipe.RecipeType; +import me.mehboss.utils.data.BrewingRecipeData; +import me.mehboss.utils.data.CookingRecipeData; +import me.mehboss.utils.data.CraftingRecipeData; +import me.mehboss.utils.data.WorkstationRecipeData; + +/** + * Builds the GUI for viewing and editing recipes using the modular GUI + * framework. + */ +public class RecipeViewBuilder { + + public GuiView buildViewing(Recipe recipe) { + return build(recipe, false, null); + } + + public GuiView buildEditing(Recipe recipe, Player p) { + return build(recipe, true, p); + } + + private GuiView build(Recipe recipe, boolean editing, Player p) { + + RecipeType type = recipe.getType(); + RecipeLayout layout = RecipeLayout.forType(type); + + GuiView view = new GuiView(54, + (editing ? ChatColor.GREEN + "EDITING: " : ChatColor.RED + "VIEWING: ") + safeString(recipe.getName())); + + /* Background */ + fillBackground(view, layout); + + /* Ingredients */ + setupIngredientSlots(view, recipe, layout, p); + setupBrewingItem(view, recipe, layout, p); + + /* Result */ + setupResultSlot(view, recipe, layout, p); + view.setEditing(editing); + + if (!editing) { + setupViewingControls(view); + } else { + setupEditingControls(view, recipe, layout, p); + } + + return view; + } + + private void fillBackground(GuiView view, RecipeLayout layout) { + + ItemStack pane = RecipeItemFactory.button(XMaterial.BLACK_STAINED_GLASS_PANE, " "); + + boolean[] blocked = new boolean[54]; + for (int i = 0; i < 54; i++) + blocked[i] = true; + + blocked[layout.getResultSlot()] = false; + + for (int slot : layout.getIngredientSlots()) + blocked[slot] = false; + + if (layout.usesFuel()) + blocked[layout.getFuelSlot()] = false; + + for (int slot = 0; slot < 54; slot++) { + if (blocked[slot]) { + view.addButton(new GuiButton(slot, pane) { + @Override + public void onClick(Player p, GuiView v, InventoryClickEvent e) { + } + }); + } + } + } + + private void setupIngredientSlots(GuiView view, Recipe recipe, RecipeLayout layout, Player p) { + + List ingredients = recipe.getIngredients(); + + // New recipe → keep ingredient slots empty (AIR) + if (ingredients == null || ingredients.isEmpty()) + return; + + int[] slots = layout.getIngredientSlots(); + int index = 0; + + for (Ingredient ingredient : ingredients) { + + if (index >= slots.length) + break; + + ItemStack icon = ingredientItem(ingredient, p); + if (icon == null) + icon = null; // leave AIR + + final int slot = slots[index]; + view.getInventory().setItem(slot, icon); + + index++; + } + } + + private void setupBrewingItem(GuiView view, Recipe recipe, RecipeLayout layout, Player p) { + + if (recipe.getType() != RecipeType.BREWING_STAND) + return; + + BrewingRecipeData brewing = (BrewingRecipeData) recipe; + ItemStack requiredItem = brewing.requiresBottles() ? new ItemStack(brewing.getRequiredBottleType()) : null; + int slot = layout.getFuelSlot(); + + // New recipe → keep ingredient slots empty (AIR) + if (requiredItem == null) + return; + + view.getInventory().setItem(slot, requiredItem); + } + + private ItemStack ingredientItem(Ingredient ing, Player p) { + + if (ing == null) + return null; + + ItemStack base = null; + + if (ing.hasIdentifier()) { + Recipe r = Main.getInstance().recipeUtil.getRecipeFromKey(ing.getIdentifier()); + if (r != null) + base = r.getResult(); + else + base = Main.getInstance().recipeUtil.getResultFromKey(ing.getIdentifier()); + } else if (ing.getMaterial() != null) { + base = XMaterial.matchXMaterial(ing.getMaterial().toString()).get().parseItem(); + + if (ing.hasMaterialData()) + base = ing.getMaterialData().toItemStack(); + + if (ing.hasDisplayName()) { + ItemMeta meta = base.getItemMeta(); + if (Main.getInstance().serverVersionAtLeast(1, 20, 5)) + meta.setItemName(ing.getDisplayName()); + else + meta.setDisplayName(ing.getDisplayName()); + base.setItemMeta(meta); + } + } + + if (base == null) + return null; + + base.setAmount(ing.getAmount()); + return base; + } + + private void setupResultSlot(GuiView view, Recipe recipe, RecipeLayout layout, Player p) { + + // New recipe → leave result slot empty + if (recipe.getResult() == null) + return; + + ItemStack result = recipe.getResult().clone(); + view.getInventory().setItem(layout.getResultSlot(), result); + } + + private void setupViewingControls(GuiView view) { + ItemStack back = RecipeItemFactory.button(XMaterial.RED_STAINED_GLASS_PANE, "&cBack"); + + for (int slot : new int[] { 48, 49, 50 }) { + view.addButton(new GuiButton(slot, back) { + @Override + public void onClick(Player p, GuiView v, InventoryClickEvent e) { + p.closeInventory(); + } + }); + } + } + + private void setupEditingControls(GuiView view, Recipe recipe, RecipeLayout layout, Player p) { + + BrewingRecipeData brewing = recipe instanceof BrewingRecipeData ? (BrewingRecipeData) recipe : null; + CookingRecipeData cooking = recipe instanceof CookingRecipeData ? (CookingRecipeData) recipe : null; + WorkstationRecipeData workstation = recipe instanceof WorkstationRecipeData ? (WorkstationRecipeData) recipe + : null; + CraftingRecipeData crafting = recipe instanceof CraftingRecipeData ? (CraftingRecipeData) recipe : null; + + /* + * Identifier (slot 9) + */ + view.addButton(new GuiStringButton(9, "Identifier", + RecipeItemFactory.button(XMaterial.PAPER, "&fIdentifier", safeString(recipe.getKey()))) { + + @Override + public void onStringChange(Player player, String newValue) { + updateSingleLineLore(this, newValue); + } + }); + + /* + * Lore Editor (slot 44) + */ + ItemStack resultItem = recipe.getResult(); + List lore = (resultItem != null && resultItem.hasItemMeta() && resultItem.getItemMeta().hasLore()) + ? resultItem.getItemMeta().getLore() + : new ArrayList<>(); + + view.addButton(new GuiLoreButton(44, RecipeItemFactory.button(XMaterial.BOOK, "&fLore", lore)) { + + @Override + public void onLoreChange(Player player, List newLore) { + + // Update icon + ItemStack icon = getIcon(); + ItemMeta meta = icon.getItemMeta(); + meta.setLore(newLore); + icon.setItemMeta(meta); + setIcon(icon); + + // Update result slot if exists + ItemStack result = view.getInventory().getItem(layout.getResultSlot()); + if (result != null) { + ItemMeta m = result.getItemMeta(); + m.setLore(newLore); + result.setItemMeta(m); + } + } + }); + + /* + * Effects (slot 7) + */ + view.addButton(new GuiStringButton(8, "Effects", RecipeItemFactory.button(XMaterial.GHAST_TEAR, "&fEffects", + recipe.getEffects() != null ? recipe.getEffects() : Arrays.asList("None"))) { + + @Override + public void onStringChange(Player player, String newValue) { + updateSingleLineLore(this, newValue); + } + }); + + /* + * Permission (slot 26) + */ + view.addButton(new GuiStringButton(26, "Permission", + RecipeItemFactory.button(XMaterial.ENDER_PEARL, "&fPermission", safeString(recipe.getPerm()))) { + + @Override + public void onStringChange(Player p2, String nv) { + updateSingleLineLore(this, nv); + } + }); + + /* + * Name (slot 0) + */ + view.addButton(new GuiStringButton(0, "Name", + RecipeItemFactory.button(XMaterial.WRITABLE_BOOK, "&fRecipe Name", safeString(recipe.getName()))) { + + @Override + public void onStringChange(Player p2, String nv) { + updateSingleLineLore(this, nv); + } + }); + + /* + * Group (slot 51) + */ + if (recipe.getType() == RecipeType.STONECUTTER || crafting != null) { + String group = crafting != null ? safeString(crafting.getGroup()) + : safeString(workstation != null ? workstation.getGroup() : ""); + + view.addButton( + new GuiStringButton(51, "Group", RecipeItemFactory.button(XMaterial.OAK_SIGN, "&fGroup", group)) { + + @Override + public void onStringChange(Player p2, String nv) { + updateSingleLineLore(this, nv); + } + }); + } + + /* + * Amount (slot 35) + */ + int amount = (resultItem != null ? resultItem.getAmount() : 0); + + view.addButton(new GuiStringButton(35, "Amount", + RecipeItemFactory.button(XMaterial.FEATHER, "&fAmount", String.valueOf(amount))) { + + @Override + public void onStringChange(Player p2, String nv) { + try { + int amt = Integer.parseInt(nv); + + ItemStack icon = getIcon(); + ItemMeta meta = icon.getItemMeta(); + meta.setDisplayName(ChatColor.GRAY + "Amount: " + amt); + icon.setItemMeta(meta); + setIcon(icon); + + ItemStack result = view.getInventory().getItem(layout.getResultSlot()); + if (result != null) + result.setAmount(amt); + + } catch (Exception ex) { + p2.sendMessage(ChatColor.RED + "Amount must be a number!"); + } + } + }); + + /* + * Placeable toggle (slot 8) + */ + view.addButton(new GuiToggleButton(17, recipe.isPlaceable(), "Placeable", + RecipeItemFactory.button(XMaterial.DIRT, "&fPlaceable")) { + @Override + public void onToggle(Player p2, boolean val) { + } + }); + + /* + * Shapeless toggle (slot 52) + */ + if (recipe.getType() == RecipeType.SHAPED || recipe.getType() == RecipeType.SHAPELESS) { + + boolean isShapeless = recipe.getType() == RecipeType.SHAPELESS; + + view.addButton(new GuiToggleButton(52, isShapeless, "Shapeless", + RecipeItemFactory.button(XMaterial.CRAFTING_TABLE, "&fShapeless")) { + + @Override + public void onToggle(Player p2, boolean val) { + } + }); + } + + /* + * Exact Choice toggle (slot 18) + */ + view.addButton(new GuiToggleButton(18, recipe.isExactChoice(), "Exact-Choice", + RecipeItemFactory.button(XMaterial.LEVER, "&fExact-Choice")) { + + @Override + public void onToggle(Player p2, boolean val) { + } + }); + + /* + * Enabled toggle (slot 53) + */ + view.addButton(new GuiToggleButton(53, recipe.isActive(), "Enabled", + RecipeItemFactory.button(recipe.isActive() ? XMaterial.SLIME_BALL : XMaterial.SNOWBALL, + recipe.isActive() ? "&aEnabled" : "&cDisabled")) { + + @Override + public void onToggle(Player p2, boolean val) { + } + }); + + /* + * Cook Time (slot 14) + */ + if (cooking != null) { + view.addButton(new GuiStringButton(43, "Cook-Time", RecipeItemFactory.button(XMaterial.CLOCK, "&fCook Time", + String.valueOf(cooking != null ? cooking.getCookTime() : 0))) { + + @Override + public void onStringChange(Player player, String nv) { + applyIntLore(this, nv, "Cook Time must be a number!"); + } + }); + } + + /* + * Experience (slot 15) + */ + if (cooking != null || recipe.getType() == RecipeType.GRINDSTONE) + view.addButton(new GuiStringButton(52, "Experience", RecipeItemFactory.button(XMaterial.EXPERIENCE_BOTTLE, + "&fExperience", String.valueOf(cooking != null ? cooking.getExperience() : 0))) { + + @Override + public void onStringChange(Player player, String nv) { + try { + double d = Double.parseDouble(nv); + ItemStack i = getIcon(); + ItemMeta m = i.getItemMeta(); + m.setLore(Arrays.asList(ChatColor.GRAY + String.valueOf(d))); + i.setItemMeta(m); + setIcon(i); + } catch (Exception ex) { + player.sendMessage(ChatColor.RED + "Experience must be a decimal number!"); + } + } + }); + + /* + * Fuel Set (slot 16) + */ + if (recipe.getType() == RecipeType.BREWING_STAND) { + view.addButton(new GuiStringButton(43, "Fuel-Set", RecipeItemFactory.button(XMaterial.COAL, "&fFuel Set", + String.valueOf(brewing != null ? brewing.getBrewFuelSet() : 0))) { + + @Override + public void onStringChange(Player player, String nv) { + applyIntLore(this, nv, "Fuel Set must be a number!"); + } + }); + } + + /* + * Repair Cost (slot 23) + */ + if (recipe.getType() == RecipeType.ANVIL || recipe.getType() == RecipeType.GRINDSTONE) + view.addButton(new GuiStringButton(51, "Repair-Cost", RecipeItemFactory.button(XMaterial.ANVIL, + "&fRepair Cost", String.valueOf(workstation != null ? workstation.getRepairCost() : 0))) { + + @Override + public void onStringChange(Player player, String nv) { + applyIntLore(this, nv, "Repair Cost must be a number!"); + } + }); + + /* + * Fuel Charge (slot 25) + */ + if (recipe.getType() == RecipeType.BREWING_STAND) { + view.addButton(new GuiStringButton(52, "Fuel-Charge", RecipeItemFactory.button(XMaterial.FIRE_CHARGE, + "&fFuel Charge", String.valueOf(brewing != null ? brewing.getBrewFuelCharge() : 0))) { + + @Override + public void onStringChange(Player player, String nv) { + applyIntLore(this, nv, "Fuel Charge must be a number!"); + } + }); + + /* + * Required Bottles (slot 10) + */ + view.addButton(new GuiToggleButton(15, brewing != null && brewing.requiresBottles(), "Required-Items", + RecipeItemFactory.button(XMaterial.BREWING_STAND, "&fRequired Items")) { + + @Override + public void onToggle(Player p2, boolean val) { + } + }); + } + + /* + * Delete / Cancel / Save buttons (45,48,49,50) + */ + view.addButton(new GuiButton(45, RecipeItemFactory.button(XMaterial.BARRIER, "&cDelete Recipe")) { + @Override + public void onClick(Player p2, GuiView v, InventoryClickEvent e) { + } + }); + + view.addButton(new GuiButton(48, RecipeItemFactory.button(XMaterial.RED_STAINED_GLASS_PANE, "&cCancel")) { + @Override + public void onClick(Player p2, GuiView v, InventoryClickEvent e) { + } + }); + + view.addButton(new GuiButton(49, RecipeItemFactory.button(XMaterial.WHITE_STAINED_GLASS_PANE, "&fMain Menu")) { + @Override + public void onClick(Player p2, GuiView v, InventoryClickEvent e) { + } + }); + + view.addButton(new GuiButton(50, RecipeItemFactory.button(XMaterial.GREEN_STAINED_GLASS_PANE, "&aUpdate")) { + @Override + public void onClick(Player p2, GuiView v, InventoryClickEvent e) { + } + }); + } + + private void updateSingleLineLore(GuiButton btn, String text) { + ItemStack icon = btn.getIcon(); + ItemMeta meta = icon.getItemMeta(); + meta.setLore(Arrays.asList(ChatColor.GRAY + text)); + icon.setItemMeta(meta); + btn.setIcon(icon); + } + + private void applyIntLore(GuiButton btn, String newValue, String errorMsg) { + try { + int val = Integer.parseInt(newValue.replace(" ", "")); + ItemStack icon = btn.getIcon(); + ItemMeta meta = icon.getItemMeta(); + meta.setLore(Arrays.asList(ChatColor.GRAY + String.valueOf(val))); + icon.setItemMeta(meta); + btn.setIcon(icon); + } catch (Exception ex) { + // caller handles sending error message + } + } + + private String safeString(String s) { + return (s == null || s.isEmpty()) ? "None" : s; + } +} \ No newline at end of file diff --git a/src/me/mehboss/gui/framework/GuiButton.java b/src/me/mehboss/gui/framework/GuiButton.java new file mode 100644 index 0000000..c1b1020 --- /dev/null +++ b/src/me/mehboss/gui/framework/GuiButton.java @@ -0,0 +1,146 @@ +package me.mehboss.gui.framework; + +import java.util.List; + +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import me.mehboss.gui.framework.chat.ChatEditManager; + +/** + * Base GUI button. Each button occupies a single slot and has its own click + * behavior. + */ +public abstract class GuiButton { + + protected final int slot; + protected ItemStack icon; + + public GuiButton(int slot, ItemStack icon) { + this.slot = slot; + this.icon = icon; + } + + public int getSlot() { + return slot; + } + + public ItemStack getIcon() { + return icon; + } + + public void setIcon(ItemStack icon) { + this.icon = icon; + } + + /** + * Called when this button is clicked. + */ + public abstract void onClick(Player player, GuiView view, InventoryClickEvent e); + + /** + * A lore editing button that triggers multiline edit mode. + */ + public static abstract class GuiLoreButton extends GuiButton { + + public GuiLoreButton(int slot, ItemStack icon) { + super(slot, icon); + } + + @Override + public void onClick(Player player, GuiView view, InventoryClickEvent e) { + ChatEditManager.get().beginLoreEdit(player, view, this); + player.closeInventory(); + } + + /** + * Fired after the lore editing session has been completed. + */ + public abstract void onLoreChange(Player player, List newLore); + } + + /** + * Button that triggers chat-based string editing. + */ + public static abstract class GuiStringButton extends GuiButton { + + private final String fieldName; + + public GuiStringButton(int slot, String fieldName, ItemStack icon) { + super(slot, icon); + this.fieldName = fieldName; + } + + public String getFieldName() { + return fieldName; + } + + @Override + public void onClick(Player player, GuiView view, InventoryClickEvent e) { + ChatEditManager.get().beginStringEdit(player, view, this); + player.closeInventory(); + } + + /** + * Fired after a new value is submitted. + */ + public abstract void onStringChange(Player player, String newValue); + } + + /** + * A clickable boolean toggle button. + */ + public static abstract class GuiToggleButton extends GuiButton { + + protected boolean value; + protected String label; + + public GuiToggleButton(int slot, boolean value, String label, ItemStack icon) { + super(slot, icon); + this.value = value; + this.label = label; + updateIcon(); + } + + public boolean getValue() { + return value; + } + + public String getFieldName() { + return label; + } + + public void toggle() { + value = !value; + updateIcon(); + } + + /** + * Updates the display name to reflect the boolean value. + */ + public void updateIcon() { + if (icon == null) + return; + + ItemMeta meta = icon.getItemMeta(); + meta.setDisplayName( + ChatColor.translateAlternateColorCodes('&', "&f" + label + ": " + (value ? "&atrue" : "&cfalse"))); + icon.setItemMeta(meta); + } + + @Override + public void onClick(Player player, GuiView view, InventoryClickEvent e) { + toggle(); + view.updateButton(this); + onToggle(player, value); + } + + /** + * Fired whenever the value is toggled. + */ + public abstract void onToggle(Player player, boolean newValue); + } +} diff --git a/src/me/mehboss/gui/framework/GuiListener.java b/src/me/mehboss/gui/framework/GuiListener.java new file mode 100644 index 0000000..7bccb9a --- /dev/null +++ b/src/me/mehboss/gui/framework/GuiListener.java @@ -0,0 +1,86 @@ +package me.mehboss.gui.framework; + +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.event.player.AsyncPlayerChatEvent; + +import me.mehboss.gui.framework.GuiView.GuiRegistry; +import me.mehboss.gui.framework.chat.ChatEditManager; + +/** + * Routes click events to the correct modular GUI controller. + */ + +public class GuiListener implements Listener { + + @EventHandler + public void onInventoryClick(InventoryClickEvent e) { + + if (!(e.getWhoClicked() instanceof Player)) + return; + + Player player = (Player) e.getWhoClicked(); + GuiView view = GuiRegistry.get(player.getUniqueId()); + + if (view == null || e.getClickedInventory() == null) + return; + + // Only block clicks inside the GUI inventory + if (!e.getClickedInventory().equals(view.getInventory())) + return; + + GuiButton btn = view.getButton(e.getRawSlot()); + + // Cancel ALL clicks for booklet + if (!view.isEditing()) { + e.setCancelled(true); + + if (btn == null) { + view.handleRecipeClick(player, e); + } + } + if (btn == null) + return; + + // Forward the click to the GUI's handler + e.setCancelled(true); + view.handleClick(player, e); + } + + @EventHandler + public void onInventoryClose(InventoryCloseEvent e) { + + if (!(e.getPlayer() instanceof Player)) + return; + + if (e.getInventory() == null || e.getInventory().getType() != InventoryType.CHEST) + return; + + Player player = (Player) e.getPlayer(); + + GuiView view = GuiRegistry.get(player.getUniqueId()); + boolean viewMatches = view != null && e.getInventory().equals(view.getInventory()); + boolean hasChatSession = ChatEditManager.get().hasSession(player.getUniqueId()); + + // Only unregister if the *closed* top inventory is the one our view is using + if (viewMatches && !hasChatSession) + GuiRegistry.unregister(player.getUniqueId()); + } + + @EventHandler + public void onChat(AsyncPlayerChatEvent e) { + + Player player = e.getPlayer(); + + if (!ChatEditManager.get().hasSession(player.getUniqueId())) + return; // Player not editing a field + + e.setCancelled(true); // Prevent broadcast + + ChatEditManager.get().handleChat(e); + } +} \ No newline at end of file diff --git a/src/me/mehboss/gui/framework/GuiView.java b/src/me/mehboss/gui/framework/GuiView.java new file mode 100644 index 0000000..0fe2806 --- /dev/null +++ b/src/me/mehboss/gui/framework/GuiView.java @@ -0,0 +1,134 @@ +package me.mehboss.gui.framework; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.Inventory; + +import me.mehboss.recipe.Main; + +/** + * Represents a GUI view (inventory window) with registered buttons. + */ +public class GuiView { + + private final Inventory inventory; + private final Map buttons = new HashMap<>(); + private boolean editing; + + public GuiView(int size, String title) { + this.inventory = Bukkit.createInventory(null, size, title); + } + + public Inventory getInventory() { + return inventory; + } + + public void open(Player player) { + GuiRegistry.register(player.getUniqueId(), this); + player.openInventory(inventory); + } + + public void addButton(GuiButton button) { + buttons.put(button.getSlot(), button); + inventory.setItem(button.getSlot(), button.getIcon()); + } + + public GuiButton getButton(int slot) { + return buttons.get(slot); + } + + /** + * Replaces the button icon after a toggle or update. + */ + public void updateButton(GuiButton button) { + inventory.setItem(button.getSlot(), button.getIcon()); + } + + /** + * Called by listener when player clicks in GUI. + */ + public void handleClick(Player player, InventoryClickEvent e) { + GuiButton button = buttons.get(e.getSlot()); + if (button != null) { + button.onClick(player, this, e); + } + } + + /** + * Forwards recipe-click handling to the global RecipeGUI. + * Returns true if the click opened another recipe. + */ + public void handleRecipeClick(Player player, InventoryClickEvent e) { + Main.getInstance().editItem.handleRecipeLinkClick(player, e.getCurrentItem()); + } + + public boolean onRawClick(Player player, InventoryClickEvent e) { + return false; // Default: no raw click handling + } + + public void setEditing(boolean editing) { + this.editing = editing; + } + + public boolean isEditing() { + return editing; + } + + /** + * Keeps track of active GUI views. + * Allows listener to route click events to the correct GUI controller. + */ + public static class GuiRegistry { + + /** All active GUI views keyed by player UUID */ + private static final Map ACTIVE = new HashMap<>(); + private static final Map ROOT_VIEW = new HashMap<>(); + + /** Register a view as currently open for a player */ + public static void register(UUID uuid, GuiView view) { + ACTIVE.put(uuid, view); + } + + /** Unregister a view when a player closes */ + public static void unregister(UUID uuid) { + ACTIVE.remove(uuid); + } + + /** Get the view a player currently has open */ + public static GuiView get(UUID uuid) { + return ACTIVE.get(uuid); + } + + + public static void setRootView(UUID uuid, GuiView view) { + ROOT_VIEW.put(uuid, view); + } + + public static GuiView getRootView(UUID uuid) { + return ROOT_VIEW.get(uuid); + } + + public static boolean hasRootView(UUID uuid) { + return ROOT_VIEW.get(uuid) != null; + } + + public static void clearRootView(UUID uuid) { + ROOT_VIEW.remove(uuid); + } + + /** + * Returns an immutable map of all active GUI views. + * Key = player UUID + * Value = GuiView instance + */ + public static Map getActiveViews() { + return Collections.unmodifiableMap(ACTIVE); + } + } +} \ No newline at end of file diff --git a/src/me/mehboss/gui/framework/RecipeGUI.java b/src/me/mehboss/gui/framework/RecipeGUI.java new file mode 100644 index 0000000..c476ac7 --- /dev/null +++ b/src/me/mehboss/gui/framework/RecipeGUI.java @@ -0,0 +1,189 @@ +package me.mehboss.gui.framework; + +import java.io.File; +import java.util.List; + +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.ItemStack; + +import com.cryptomorin.xseries.XMaterial; +import me.mehboss.commands.CommandRemove; +import me.mehboss.gui.RecipeSaver; +import me.mehboss.gui.RecipeViewBuilder; +import me.mehboss.gui.framework.GuiView.GuiRegistry; +import me.mehboss.recipe.Main; +import me.mehboss.utils.RecipeUtil.Recipe; + +/** + * Orchestrator for opening/editing/deleting recipes. This class is now small & + * clean because the GUI framework handles everything else. + */ +public class RecipeGUI { + + private final RecipeViewBuilder builder = new RecipeViewBuilder(); + private final RecipeSaver saver = new RecipeSaver(); + + public RecipeGUI() { + } + + /** + * Called when the user clicks an item inside VIEW mode (booklet or GUI) and we + * want to open the recipe that produces that item. + * + * @return true if click was handled (a recipe was opened) + */ + public void handleRecipeLinkClick(Player player, ItemStack clickedItem) { + + if (clickedItem == null || clickedItem.getType() == Material.AIR) + return; + + // Look up recipe from the API + Recipe linked = Main.getInstance().recipeUtil.getRecipeFromResult(clickedItem); + if (linked == null) + return; // not a recipe result, ignore + + // OPEN the linked recipe in viewing mode + openViewing(player, linked); + return; + } + + /** + * Opens the VIEWING GUI for a recipe. + */ + public void openViewing(Player player, Recipe recipe) { + GuiView view = builder.buildViewing(recipe); + if (!GuiRegistry.hasRootView(player.getUniqueId())) + GuiRegistry.setRootView(player.getUniqueId(), view); + + // Back buttons (48, 49, 50) -> close and open type GUI + for (int slot : new int[] { 48, 49, 50 }) { + overrideOn(view, slot, (p, v, e) -> { + p.closeInventory(); + + GuiView root = GuiRegistry.getRootView(player.getUniqueId()); + if (root != null && root != view) { + root.open(player); + } else { + List types = Main.getInstance().recipes.buildRecipesFor(p, recipe.getType()); + if (types == null || types.isEmpty()) { + Main.getInstance().typeGUI.open(p); + } else { + Main.getInstance().recipes.openType(p, recipe.getType()); + } + + GuiRegistry.clearRootView(player.getUniqueId()); + } + }); + } + view.open(player); + } + + /** + * Opens the EDITING GUI for a recipe. + */ + public void openEditing(Player player, Recipe recipe) { + GuiView view = builder.buildEditing(recipe, player); + attachEditingHandlers(view, recipe); + + view.open(player); + } + + private void attachEditingHandlers(GuiView view, Recipe recipe) { + + /* -------- Update / Save Recipe (slot 50) -------- */ + overrideOn(view, 50, (player, v, e) -> { + saver.saveRecipe(v, player, recipe); + player.sendMessage(ChatColor.GREEN + "Recipe updated."); + }); + + /* -------- Delete Recipe (slot 45) -------- */ + overrideOn(view, 45, handleDeleteClick(recipe)); + + /* -------- Cancel (slot 48) -------- */ + overrideOn(view, 48, (player, v, e) -> { + player.closeInventory(); + player.sendMessage(ChatColor.RED + "Canceled recipe edit."); + }); + + /* -------- Main Menu (slot 49) -------- */ + overrideOn(view, 49, (p, v, e) -> { + p.closeInventory(); + List types = Main.getInstance().recipes.buildRecipesFor(p, recipe.getType()); + if (types == null || types.isEmpty()) { + Main.getInstance().typeGUI.open(p); + } else { + Main.getInstance().recipes.openType(p, recipe.getType()); + } + }); + } + + private GuiButtonClick handleDeleteClick(Recipe recipe) { + return (player, view, e) -> { + + GuiButton btn = view.getButton(45); + if (btn == null || btn.getIcon() == null || !btn.getIcon().hasItemMeta()) { + return; + } + + if (!recipe.hasKey()) + return; + + String title = ChatColor.stripColor(btn.getIcon().getItemMeta().getDisplayName()); + + if (!"Confirm Recipe Deletion".equals(title)) { + + ItemStack confirm = RecipeItemFactory.button(XMaterial.BARRIER, "&cConfirm Recipe Deletion"); + + btn.setIcon(confirm); + view.addButton(btn); // refresh in view + return; + } + + // Delete file from disk + File f = new File(Main.getInstance().getDataFolder(), "recipes/" + recipe.getName() + ".yml"); + + if (f.exists()) { + if (f.delete()) { + CommandRemove.removeRecipe(recipe.getName()); + player.sendMessage(ChatColor.GREEN + "Recipe deleted successfully."); + } else { + player.sendMessage(ChatColor.RED + "Failed to delete recipe file."); + } + } + + player.closeInventory(); + Main.getInstance().typeGUI.open(player); + }; + } + + @FunctionalInterface + private interface GuiButtonClick { + void click(Player player, GuiView view, InventoryClickEvent e); + } + + /** + * Replaces the button at the given slot in the given view with a new GuiButton + * that uses the same icon but calls the provided handler on click. + */ + private void overrideOn(GuiView view, int slot, GuiButtonClick handler) { + + GuiButton oldBtn = view.getButton(slot); + if (oldBtn == null) + return; + + ItemStack icon = oldBtn.getIcon(); + + GuiButton newBtn = new GuiButton(slot, icon) { + @Override + public void onClick(Player player, GuiView v, InventoryClickEvent e) { + handler.click(player, v, e); + } + }; + + // Replace in this specific view only – no registry scanning + view.addButton(newBtn); + } +} \ No newline at end of file diff --git a/src/me/mehboss/gui/framework/RecipeItemFactory.java b/src/me/mehboss/gui/framework/RecipeItemFactory.java new file mode 100644 index 0000000..c416ac4 --- /dev/null +++ b/src/me/mehboss/gui/framework/RecipeItemFactory.java @@ -0,0 +1,60 @@ +package me.mehboss.gui.framework; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import org.bukkit.ChatColor; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import com.cryptomorin.xseries.XMaterial; + +/** + * A helper class for clean, consistent ItemStack creation. + */ +public class RecipeItemFactory { + + public static ItemStack button(ItemStack itemStack, String name, String... lore) { + ItemStack item = new ItemStack(itemStack); + ItemMeta meta = item.getItemMeta(); + + meta.setDisplayName(color(name)); + + if (lore != null && lore.length > 0) { + meta.setLore(colorList(Arrays.asList(lore))); + } + + item.setItemMeta(meta); + return item; + } + + public static ItemStack button(XMaterial mat, String name, String... lore) { + return button(mat.parseItem(), name, lore); + } + + public static ItemStack button(ItemStack mat, String name, List lore) { + ItemStack item = new ItemStack(mat); + ItemMeta meta = item.getItemMeta(); + + meta.setDisplayName(color(name)); + if (lore != null) { + meta.setLore(colorList(lore)); + } + + item.setItemMeta(meta); + return item; + } + + public static ItemStack button(XMaterial mat, String name, List lore) { + return button(mat.parseItem(), name, lore); + } + + private static String color(String s) { + return ChatColor.translateAlternateColorCodes('&', s); + } + + private static List colorList(List list) { + return list.stream().map(RecipeItemFactory::color).collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/src/me/mehboss/gui/framework/RecipeLayout.java b/src/me/mehboss/gui/framework/RecipeLayout.java new file mode 100644 index 0000000..c46cbbe --- /dev/null +++ b/src/me/mehboss/gui/framework/RecipeLayout.java @@ -0,0 +1,68 @@ +package me.mehboss.gui.framework; + +import me.mehboss.utils.RecipeUtil.Recipe.RecipeType; + +/** + * Defines where ingredients, fuel, result, etc. should be placed for each recipe type. + */ +public class RecipeLayout { + + private final int[] ingredientSlots; + private final Integer fuelSlot; + private final int resultSlot; + + public RecipeLayout(int[] ingredientSlots, Integer fuelSlot, int resultSlot) { + this.ingredientSlots = ingredientSlots; + this.fuelSlot = fuelSlot; + this.resultSlot = resultSlot; + } + + public int[] getIngredientSlots() { + return ingredientSlots; + } + + public Integer getFuelSlot() { + return fuelSlot; + } + + public boolean usesFuel() { + return fuelSlot != null; + } + + public int getResultSlot() { + return resultSlot; + } + + /* ========= Static Layout Definitions ========= */ + + public static RecipeLayout forType(RecipeType type) { + + int[] CRAFT_GRID = { 11, 12, 13, 20, 21, 22, 29, 30, 31 }; + + switch (type) { + + case SHAPED: + case SHAPELESS: + return new RecipeLayout(CRAFT_GRID, null, 24); + + case STONECUTTER: + case CAMPFIRE: + return new RecipeLayout(new int[] { 20 }, null, 24); + + case FURNACE: + case BLASTFURNACE: + case SMOKER: + return new RecipeLayout(new int[] { 11 }, 29, 24); + + case ANVIL: + case GRINDSTONE: + return new RecipeLayout(new int[] { 20, 22 }, null, 24); + + case BREWING_STAND: + return new RecipeLayout(new int[] { 20, 22 }, 24, 40); + + default: + return new RecipeLayout(CRAFT_GRID, null, 24); + } + } +} diff --git a/src/me/mehboss/gui/framework/chat/ChatEditManager.java b/src/me/mehboss/gui/framework/chat/ChatEditManager.java new file mode 100644 index 0000000..53c2015 --- /dev/null +++ b/src/me/mehboss/gui/framework/chat/ChatEditManager.java @@ -0,0 +1,241 @@ +package me.mehboss.gui.framework.chat; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.event.player.AsyncPlayerChatEvent; + +import me.mehboss.gui.framework.GuiButton.GuiLoreButton; +import me.mehboss.gui.framework.GuiButton.GuiStringButton; +import me.mehboss.gui.framework.GuiView; + +/** + * Handles chat-based editing of strings and lore for GUI buttons. + */ +public class ChatEditManager { + + private static final ChatEditManager INSTANCE = new ChatEditManager(); + + public static ChatEditManager get() { + return INSTANCE; + } + + private final Map sessions = new HashMap<>(); + + private ChatEditManager() { + } + + public void beginStringEdit(Player player, GuiView view, GuiStringButton button) { + ChatEditSession session = new ChatEditSession(player, view, button); + sessions.put(player.getUniqueId(), session); + + player.sendMessage(ChatColor.DARK_GRAY + "---------------------------------------------"); + player.sendMessage("Please type your new value for " + ChatColor.GREEN + button.getFieldName().toLowerCase()); + player.sendMessage("Type " + ChatColor.RED + "CANCEL " + ChatColor.WHITE + "to go back."); + player.sendMessage("Type " + ChatColor.GREEN + "DONE " + ChatColor.WHITE + "when finished."); + player.sendMessage(ChatColor.DARK_GRAY + "---------------------------------------------"); + } + + public void beginLoreEdit(Player player, GuiView view, GuiLoreButton button) { + List currentLore = null; + if (button.getIcon() != null && button.getIcon().hasItemMeta() && button.getIcon().getItemMeta().hasLore()) { + currentLore = button.getIcon().getItemMeta().getLore(); + } + + ChatEditSession session = new ChatEditSession(player, view, button, currentLore); + sessions.put(player.getUniqueId(), session); + + sendLoreInstructions(player, currentLore); + } + + private void sendLoreInstructions(Player p, List lore) { + int num = 1; + + p.sendMessage(ChatColor.DARK_GRAY + "---------------------------------------------"); + p.sendMessage("Please type your new recipe lore"); + p.sendMessage("Current Lore: "); + p.sendMessage(" "); + + if (lore != null && !lore.isEmpty()) { + for (String line : lore) { + p.sendMessage(num + ": " + line); + num++; + } + } else { + p.sendMessage("NO LORE"); + } + + p.sendMessage(" "); + p.sendMessage("Type 'LINE#: string' to edit/add a line to your lore."); + p.sendMessage("Type 'LINE#: remove' to remove a line."); + p.sendMessage("Type ONLY the line number for an empty space."); + p.sendMessage("Type " + ChatColor.RED + "CANCEL " + ChatColor.WHITE + "to go back."); + p.sendMessage("Type " + ChatColor.GREEN + "DONE " + ChatColor.WHITE + "when finished."); + p.sendMessage(ChatColor.DARK_GRAY + "---------------------------------------------"); + } + + public boolean hasSession(UUID uuid) { + return sessions.containsKey(uuid); + } + + public void cancel(Player player) { + if (player == null) + return; + sessions.remove(player.getUniqueId()); + } + + public void handleChat(AsyncPlayerChatEvent e) { + Player player = e.getPlayer(); + UUID uuid = player.getUniqueId(); + + if (!sessions.containsKey(uuid)) { + return; + } + + e.setCancelled(true); + + ChatEditSession session = sessions.get(uuid); + if (session == null) { + return; + } + + switch (session.getType()) { + case STRING: + handleStringChat(e, session); + break; + case LORE: + handleLoreChat(e, session); + break; + } + } + + private void handleStringChat(AsyncPlayerChatEvent e, ChatEditSession session) { + Player p = session.getPlayer(); + String msg = e.getMessage(); + + // Cancel -> discard and reopen GUI + if (msg.equalsIgnoreCase("cancel")) { + endSessionAndReopen(session); + return; + } + + // Confirm -> apply pending value + if (msg.equalsIgnoreCase("done")) { + if (session.getPendingValue() == null) { + p.sendMessage(ChatColor.RED + "No value selected yet. Type something first or CANCEL."); + return; + } + + String newValue = session.getPendingValue(); + GuiStringButton button = session.getStringButton(); + + // Let button handle updating its icon / backing data + button.onStringChange(p, newValue); + session.getReturnView().updateButton(button); + + // Reopen GUI + endSessionAndReopen(session); + return; + } + + // Any other text is considered new value + session.setPendingValue(msg); + + p.sendMessage(" "); + p.sendMessage(ChatColor.DARK_GRAY + "---------------------------------------------"); + p.sendMessage(ChatColor.GRAY + ChatColor.translateAlternateColorCodes('&', msg) + ChatColor.WHITE + + " has been selected as the new " + buttonFieldName(session) + "."); + p.sendMessage("Type " + ChatColor.GREEN + "DONE " + ChatColor.WHITE + "to confirm your selection!"); + p.sendMessage("Type " + ChatColor.RED + "CANCEL " + ChatColor.WHITE + "to cancel your selection!"); + p.sendMessage(ChatColor.GRAY + "Or retype selection for a different " + buttonFieldName(session)); + p.sendMessage(ChatColor.DARK_GRAY + "---------------------------------------------"); + } + + private String buttonFieldName(ChatEditSession session) { + GuiStringButton b = session.getStringButton(); + return b != null ? b.getFieldName().toLowerCase() : "value"; + } + + private void handleLoreChat(AsyncPlayerChatEvent e, ChatEditSession session) { + Player p = session.getPlayer(); + String msg = e.getMessage(); + List lore = session.getLoreBuffer(); + + if (msg.equalsIgnoreCase("done")) { + // Apply final lore to button + GuiLoreButton button = session.getLoreButton(); + button.onLoreChange(p, lore); + session.getReturnView().updateButton(button); + endSessionAndReopen(session); + return; + } + + if (msg.equalsIgnoreCase("cancel")) { + endSessionAndReopen(session); + return; + } + + // Expected formats: + // "LINE#: text" + // "LINE#: remove" + // "LINE#" (empty line) + String[] spl = msg.split(": ", 2); + + if (spl.length == 0 || !isInt(spl[0]) || Integer.parseInt(spl[0]) <= 0) { + p.sendMessage(ChatColor.RED + "Wrong format! You must first define the line number!"); + return; + } + + int lineIndex = Integer.parseInt(spl[0]) - 1; + String newLine = spl.length < 2 ? null : spl[1]; + + if (lore == null) { + lore = session.getLoreBuffer(); + } + + if (lore.isEmpty() || lore.size() <= lineIndex) { + // Append new lines + if (newLine == null) { + lore.add(" "); + } else if ("remove".equalsIgnoreCase(newLine)) { + // nothing to remove, ignore + } else { + lore.add(ChatColor.translateAlternateColorCodes('&', newLine)); + } + } else { + // Modify existing + if (newLine == null) { + lore.set(lineIndex, " "); + } else if ("remove".equalsIgnoreCase(newLine)) { + lore.remove(lineIndex); + } else { + lore.set(lineIndex, ChatColor.translateAlternateColorCodes('&', newLine)); + } + } + + // Re-show state + sendLoreInstructions(p, lore); + } + + private boolean isInt(String text) { + try { + Integer.parseInt(text); + return true; + } catch (NumberFormatException ex) { + return false; + } + } + + private void endSessionAndReopen(ChatEditSession session) { + sessions.remove(session.getPlayer().getUniqueId()); + + Bukkit.getScheduler().runTask(Bukkit.getPluginManager().getPlugin("CustomRecipes"), () -> { + session.getPlayer().openInventory(session.getReturnView().getInventory()); + }); + } +} diff --git a/src/me/mehboss/gui/framework/chat/ChatEditSession.java b/src/me/mehboss/gui/framework/chat/ChatEditSession.java new file mode 100644 index 0000000..12d69fa --- /dev/null +++ b/src/me/mehboss/gui/framework/chat/ChatEditSession.java @@ -0,0 +1,87 @@ +package me.mehboss.gui.framework.chat; + +import java.util.ArrayList; +import java.util.List; + +import org.bukkit.entity.Player; + +import me.mehboss.gui.framework.GuiButton.GuiLoreButton; +import me.mehboss.gui.framework.GuiButton.GuiStringButton; +import me.mehboss.gui.framework.GuiView; + +/** + * Represents an active chat-based editing session for a player. + */ +public class ChatEditSession { + + public enum EditType { + STRING, + LORE + } + + private final Player player; + private final GuiView returnView; + private final EditType type; + + // For STRING mode + private final GuiStringButton stringButton; + private String pendingValue; + + // For LORE mode + private final GuiLoreButton loreButton; + private List loreBuffer; + + public ChatEditSession(Player player, GuiView view, GuiStringButton button) { + this.player = player; + this.returnView = view; + this.type = EditType.STRING; + this.stringButton = button; + this.loreButton = null; + this.loreBuffer = null; + } + + public ChatEditSession(Player player, GuiView view, GuiLoreButton button, List currentLore) { + this.player = player; + this.returnView = view; + this.type = EditType.LORE; + this.loreButton = button; + this.stringButton = null; + this.loreBuffer = currentLore != null ? new ArrayList<>(currentLore) : new ArrayList<>(); + } + + public Player getPlayer() { + return player; + } + + public GuiView getReturnView() { + return returnView; + } + + public EditType getType() { + return type; + } + + public GuiStringButton getStringButton() { + return stringButton; + } + + public GuiLoreButton getLoreButton() { + return loreButton; + } + + public String getPendingValue() { + return pendingValue; + } + + public void setPendingValue(String pendingValue) { + this.pendingValue = pendingValue; + } + + public List getLoreBuffer() { + return loreBuffer; + } + + public void setLoreBuffer(List loreBuffer) { + this.loreBuffer = loreBuffer; + } +} \ No newline at end of file diff --git a/src/me/mehboss/gui/package-info.java b/src/me/mehboss/gui/package-info.java new file mode 100644 index 0000000..ff7a7e0 --- /dev/null +++ b/src/me/mehboss/gui/package-info.java @@ -0,0 +1,7 @@ +/** + * Contains GUI classes used to display menus, recipe editors, and + * interactive interfaces for players. + *

    + * Provides custom inventory UIs for browsing, editing, and managing recipes. + */ +package me.mehboss.gui; \ No newline at end of file diff --git a/src/me/mehboss/listeners/package-info.java b/src/me/mehboss/listeners/package-info.java new file mode 100644 index 0000000..a55c0a1 --- /dev/null +++ b/src/me/mehboss/listeners/package-info.java @@ -0,0 +1,5 @@ +/** + * Contains global event listeners that respond to generic player or + * server events not tied to a specific workstation category. + */ +package me.mehboss.listeners; \ No newline at end of file diff --git a/src/me/mehboss/recipe/CommandListener.java b/src/me/mehboss/recipe/CommandListener.java index 6e86aa1..90b9916 100644 --- a/src/me/mehboss/recipe/CommandListener.java +++ b/src/me/mehboss/recipe/CommandListener.java @@ -19,6 +19,7 @@ import me.mehboss.commands.CommandList; import me.mehboss.commands.CommandRecipe; import me.mehboss.commands.CommandReload; +import me.mehboss.commands.CommandRemove; import me.mehboss.commands.CommandShow; public class CommandListener implements CommandExecutor { @@ -46,6 +47,10 @@ public boolean onCommand(CommandSender sender, Command command, String label, St return true; } + if (args[0].equalsIgnoreCase("remove")) { + return CommandRemove.Run(cmd); + } + if (args[0].equalsIgnoreCase("help")) { return CommandHelp.Run(cmd); } @@ -74,6 +79,7 @@ public boolean onCommand(CommandSender sender, Command command, String label, St return CommandCrafterDebug.Run(cmd); } + // anything after this must require sender to be a player if (!(sender instanceof Player)) { sender.sendMessage("You must be a player in order to use this command!"); return true; diff --git a/src/me/mehboss/recipe/ExactChoice.java b/src/me/mehboss/recipe/ExactChoice.java index 7c44606..05c1b4d 100644 --- a/src/me/mehboss/recipe/ExactChoice.java +++ b/src/me/mehboss/recipe/ExactChoice.java @@ -19,15 +19,19 @@ import org.bukkit.inventory.recipe.CookingBookCategory; import org.bukkit.inventory.recipe.CraftingBookCategory; +import me.mehboss.utils.CompatibilityUtil; import me.mehboss.utils.RecipeUtil; import me.mehboss.utils.RecipeUtil.Ingredient; import me.mehboss.utils.RecipeUtil.Recipe; +import me.mehboss.utils.data.CookingRecipeData; +import me.mehboss.utils.data.CraftingRecipeData; +import me.mehboss.utils.data.WorkstationRecipeData; public class ExactChoice { RecipeManager recipeManager = Main.getInstance().recipeManager; RecipeUtil getRecipeUtil() { - return Main.getInstance().recipeUtil; + return Main.getInstance().recipeUtil; } RecipeChoice.ExactChoice findExactChoice(Recipe recipe, Ingredient ingredient) { @@ -58,7 +62,7 @@ RecipeChoice.ExactChoice findExactChoice(Recipe recipe, Ingredient ingredient) { ItemMeta exactMeta = exactItem.getItemMeta(); if (item.hasDisplayName()) - exactMeta.setDisplayName(item.getDisplayName()); + exactMeta = CompatibilityUtil.setDisplayname(exactItem, item.getDisplayName()); if (item.hasCustomModelData()) exactMeta.setCustomModelData(item.getCustomModelData()); @@ -68,7 +72,7 @@ RecipeChoice.ExactChoice findExactChoice(Recipe recipe, Ingredient ingredient) { } } - ShapelessRecipe createShapelessRecipe(Recipe recipe) { + ShapelessRecipe createShapelessRecipe(CraftingRecipeData recipe) { ShapelessRecipe shapelessRecipe = new ShapelessRecipe(recipeManager.createNamespacedKey(recipe), recipe.getResult()); @@ -77,25 +81,29 @@ ShapelessRecipe createShapelessRecipe(Recipe recipe) { continue; // Uses exactChoice if version is 1.14 or higher - if (recipe.isExactChoice() && !recipe.getIgnoreData()) { + if (recipe.isExactChoice() && !recipe.getIgnoreData() && !ingredient.hasMaterialChoices()) { shapelessRecipe.addIngredient(findExactChoice(recipe, ingredient)); - - try { - shapelessRecipe.setCategory(CraftingBookCategory.valueOf(recipe.getBookCategory())); - } catch (NoClassDefFoundError e) { - } + } else if (ingredient.hasMaterialChoices()) { + shapelessRecipe.addIngredient(new RecipeChoice.MaterialChoice(ingredient.getMaterialChoices())); } else { shapelessRecipe.addIngredient(ingredient.getMaterial()); } } + // only if 1.13 or higher + try { + shapelessRecipe.setCategory(CraftingBookCategory.valueOf(recipe.getBookCategory())); + shapelessRecipe.setGroup(recipe.getGroup()); + } catch (NoClassDefFoundError e) { + } + if (recipe.isExactChoice()) logDebug("Created " + recipe.getType() + " recipe using the ExactChoice method.", recipe.getName()); return shapelessRecipe; } - ShapedRecipe createShapedRecipe(Recipe recipe) { + ShapedRecipe createShapedRecipe(CraftingRecipeData recipe) { ShapedRecipe shapedRecipe = new ShapedRecipe(recipeManager.createNamespacedKey(recipe), recipe.getResult()); ArrayList ingredients = new ArrayList(); @@ -104,30 +112,33 @@ ShapedRecipe createShapedRecipe(Recipe recipe) { if (ingredient.isEmpty() || ingredient.getMaterial() == Material.AIR || ingredients.contains(ingredient.getAbbreviation())) continue; - ingredients.add(ingredient.getAbbreviation()); // Uses exactChoice if version is 1.14 or higher // Ignores if IgnoreData or IgnoreModelData is true - if (recipe.isExactChoice() && !recipe.getIgnoreData()) { - shapedRecipe.setIngredient(ingredient.getAbbreviation().charAt(0), findExactChoice(recipe, ingredient)); - - try { - shapedRecipe.setCategory(CraftingBookCategory.valueOf(recipe.getBookCategory())); - } catch (NoClassDefFoundError e) { - } + char abbrev = ingredient.getAbbreviation().charAt(0); + if (recipe.isExactChoice() && !recipe.getIgnoreData() && !ingredient.hasMaterialChoices()) { + shapedRecipe.setIngredient(abbrev, findExactChoice(recipe, ingredient)); + } else if (ingredient.hasMaterialChoices()) { + shapedRecipe.setIngredient(abbrev, new RecipeChoice.MaterialChoice(ingredient.getMaterialChoices())); } else { - shapedRecipe.setIngredient(ingredient.getAbbreviation().charAt(0), ingredient.getMaterial()); + shapedRecipe.setIngredient(abbrev, ingredient.getMaterial()); } } + try { + shapedRecipe.setCategory(CraftingBookCategory.valueOf(recipe.getBookCategory())); + shapedRecipe.setGroup(recipe.getGroup()); + } catch (NoClassDefFoundError e) { + } + if (recipe.isExactChoice()) logDebug("Created " + recipe.getType() + " recipe using the ExactChoice method.", recipe.getName()); return shapedRecipe; } - FurnaceRecipe createFurnaceRecipe(Recipe recipe) { + FurnaceRecipe createFurnaceRecipe(CookingRecipeData recipe) { FurnaceRecipe furnaceRecipe; @@ -148,7 +159,7 @@ FurnaceRecipe createFurnaceRecipe(Recipe recipe) { return furnaceRecipe; } - BlastingRecipe createBlastFurnaceRecipe(Recipe recipe) { + BlastingRecipe createBlastFurnaceRecipe(CookingRecipeData recipe) { if (!Main.getInstance().serverVersionAtLeast(1, 14)) { logError("Error loading recipe. Your server version does not support BlastFurnace recipes!", @@ -167,7 +178,7 @@ BlastingRecipe createBlastFurnaceRecipe(Recipe recipe) { return blastRecipe; } - SmokingRecipe createSmokerRecipe(Recipe recipe) { + SmokingRecipe createSmokerRecipe(CookingRecipeData recipe) { if (!Main.getInstance().serverVersionAtLeast(1, 14)) { logError("Error loading recipe. Your server version does not support Smoker recipes!", recipe.getName()); return null; @@ -183,24 +194,23 @@ SmokingRecipe createSmokerRecipe(Recipe recipe) { return smokingRecipe; } - StonecuttingRecipe createStonecuttingRecipe(Recipe recipe) { + StonecuttingRecipe createStonecuttingRecipe(WorkstationRecipeData recipe) { if (!Main.getInstance().serverVersionAtLeast(1, 14)) { logError("Error loading recipe. Your server version does not support Stonecutting recipes!", recipe.getName()); return null; } - StonecuttingRecipe sRecipe = new StonecuttingRecipe(new NamespacedKey(Main.getInstance(), recipe.getKey()), recipe.getResult(), - findExactChoice(recipe, null)); + StonecuttingRecipe sRecipe = new StonecuttingRecipe(new NamespacedKey(Main.getInstance(), recipe.getKey()), + recipe.getResult(), findExactChoice(recipe, null)); sRecipe.setGroup(recipe.getGroup()); - + return sRecipe; } - - CampfireRecipe createCampfireRecipe(Recipe recipe) { + + CampfireRecipe createCampfireRecipe(CookingRecipeData recipe) { if (!Main.getInstance().serverVersionAtLeast(1, 14)) { - logError("Error loading recipe. Your server version does not support Campfire recipes!", - recipe.getName()); + logError("Error loading recipe. Your server version does not support Campfire recipes!", recipe.getName()); return null; } diff --git a/src/me/mehboss/recipe/Main.java b/src/me/mehboss/recipe/Main.java index 9c9b4ad..745dc94 100644 --- a/src/me/mehboss/recipe/Main.java +++ b/src/me/mehboss/recipe/Main.java @@ -25,7 +25,6 @@ import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.inventory.FurnaceRecipe; -import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.Recipe; import org.bukkit.inventory.ShapedRecipe; @@ -35,24 +34,31 @@ import com.cryptomorin.xseries.XMaterial; import me.mehboss.anvil.AnvilManager; +import me.mehboss.anvil.AnvilManager_1_8; import me.mehboss.anvil.GrindstoneManager; +import me.mehboss.brewing.BrewEvent; +import me.mehboss.commands.CommandRecipes; import me.mehboss.commands.TabCompletion; import me.mehboss.cooking.CookingManager; import me.mehboss.crafting.AmountManager; -import me.mehboss.crafting.CooldownManager; import me.mehboss.crafting.CraftManager; import me.mehboss.crafting.CrafterManager; -import me.mehboss.gui.RecipeGUI; +import me.mehboss.crafting.ShapedChecks; +import me.mehboss.crafting.ShapelessChecks; import me.mehboss.gui.RecipeTypeGUI; +import me.mehboss.gui.framework.GuiListener; +import me.mehboss.gui.framework.RecipeGUI; import me.mehboss.gui.BookGUI; import me.mehboss.listeners.BlockManager; import me.mehboss.listeners.EffectsManager; +import me.mehboss.utils.CooldownManager; import me.mehboss.utils.ItemBuilder; import me.mehboss.utils.MetaChecks; -import me.mehboss.utils.Metrics; import me.mehboss.utils.Placeholders; import me.mehboss.utils.RecipeUtil; -import me.mehboss.utils.UpdateChecker; +import me.mehboss.utils.CooldownManager.Cooldown; +import me.mehboss.utils.libs.Metrics; +import me.mehboss.utils.libs.UpdateChecker; import net.md_5.bungee.api.ChatMessageType; import net.md_5.bungee.api.chat.TextComponent; @@ -60,18 +66,19 @@ public class Main extends JavaPlugin implements Listener { public AmountManager amountManager; public CraftManager craftManager; public RecipeManager recipeManager; + public ShapedChecks shapedChecks; + public ShapelessChecks shapelessChecks; public BookGUI recipes; public RecipeTypeGUI typeGUI; public CooldownManager cooldownManager; public RecipeUtil recipeUtil; public ExactChoice exactChoice; - RecipeGUI editItem; + public RecipeGUI editItem; public Map debounceMap = new HashMap<>(); public ArrayList inInventory = new ArrayList(); public ArrayList recipeBook = new ArrayList(); - public HashMap saveInventory = new HashMap(); public ArrayList disabledrecipe = new ArrayList(); // add three more shapelessname, amount, and ID specifically for config. @@ -132,6 +139,11 @@ void registerCommands() { PluginCommand crecipeCommand = getCommand("crecipe"); crecipeCommand.setExecutor(new CommandListener()); + if (getConfig().getBoolean("Recipes-Alias", true)) { + PluginCommand recipesCommand = getCommand("recipes"); + recipesCommand.setExecutor(new CommandRecipes()); + } + if (serverVersionAtLeast(1, 15)) { TabCompletion tabCompleter = new TabCompletion(); crecipeCommand.setTabCompleter(tabCompleter); @@ -251,6 +263,8 @@ public void onEnable() { recipeUtil = new RecipeUtil(); recipeManager = new RecipeManager(); metaChecks = new MetaChecks(); + shapedChecks = new ShapedChecks(); + shapelessChecks = new ShapelessChecks(); craftManager = new CraftManager(); amountManager = new AmountManager(craftManager); @@ -323,11 +337,11 @@ public void run() { } }, 40L); - editItem = new RecipeGUI(this, null); + editItem = new RecipeGUI(); recipes = new BookGUI(this); typeGUI = new RecipeTypeGUI(); - Bukkit.getPluginManager().registerEvents(editItem, this); + Bukkit.getPluginManager().registerEvents(new GuiListener(), this); Bukkit.getPluginManager().registerEvents(recipes, this); Bukkit.getPluginManager().registerEvents(typeGUI, this); Bukkit.getPluginManager().registerEvents(new EffectsManager(), this); @@ -335,11 +349,16 @@ public void run() { Bukkit.getPluginManager().registerEvents(new CrafterManager(), this); Bukkit.getPluginManager().registerEvents(new AmountManager(craftManager), this); Bukkit.getPluginManager().registerEvents(new BlockManager(), this); - Bukkit.getPluginManager().registerEvents(new AnvilManager(), this); Bukkit.getPluginManager().registerEvents(new CookingManager(), this); Bukkit.getPluginManager().registerEvents(new GrindstoneManager(), this); + Bukkit.getPluginManager().registerEvents(new BrewEvent(), this); Bukkit.getPluginManager().registerEvents(this, this); + if (serverVersionAtLeast(1, 9)) + Bukkit.getPluginManager().registerEvents(new AnvilManager(), this); + else + Bukkit.getPluginManager().registerEvents(new AnvilManager_1_8(), this); + registerUpdateChecker(); registerBstats(); } @@ -355,8 +374,6 @@ public void clear() { // Clear internal state if (recipeBook != null) recipeBook.clear(); - if (saveInventory != null) - saveInventory.clear(); if (disabledrecipe != null) disabledrecipe.clear(); @@ -372,15 +389,7 @@ public void clear() { public void reload() { - // Remove old recipes - removeCustomRecipes(); - removeRecipes(); clear(); - - disableRecipes(); - - // Reload configs - reloadConfig(); customConfig = YamlConfiguration.loadConfiguration(customYml); saveCustomYml(customConfig, customYml); saveAllCustomYml(); @@ -388,9 +397,15 @@ public void reload() { // Reset managers recipeUtil = new RecipeUtil(); recipeManager = new RecipeManager(); + editItem = new RecipeGUI(); recipes = new BookGUI(this); typeGUI = new RecipeTypeGUI(); - editItem = new RecipeGUI(this, null); + + // Remove old recipes + removeCustomRecipes(); + removeRecipes(); + + disableRecipes(); // Re-add recipes immediately ItemBuilder.reload(); @@ -404,32 +419,34 @@ public void reload() { public void onDisable() { if (cooldownManager != null) { - // Check if there are any cooldowns - if (cooldownManager.getCooldowns().isEmpty() && cooldownConfig.isConfigurationSection("Cooldowns")) { - cooldownConfig.set("Cooldowns", null); - saveCustomYml(cooldownConfig, cooldownYml); - } else if (!cooldownManager.getCooldowns().isEmpty()) { - // Iterate over each player's cooldowns - for (Map.Entry> playerEntry : cooldownManager.getCooldowns().entrySet()) { - UUID playerUUID = playerEntry.getKey(); - Map playerCooldowns = playerEntry.getValue(); - - // Iterate over each recipe's cooldown for the player - for (Map.Entry recipeEntry : playerCooldowns.entrySet()) { - String recipeID = recipeEntry.getKey(); - Long cooldownTime = recipeEntry.getValue(); - - // Save the cooldown data under the player's UUID and recipe ID - cooldownConfig.set("Cooldowns." + playerUUID.toString() + "." + recipeID, cooldownTime); + + Map> allCooldowns = cooldownManager.getCooldowns(); + + // Clear the entire section first to avoid leftovers + cooldownConfig.set("Cooldowns", null); + for (Map.Entry> playerEntry : allCooldowns.entrySet()) { + + UUID playerUUID = playerEntry.getKey(); + Map playerCooldowns = playerEntry.getValue(); + + for (Map.Entry recipeEntry : playerCooldowns.entrySet()) { + + String recipeID = recipeEntry.getKey(); + Cooldown cooldown = recipeEntry.getValue(); + + // Skip expired cooldowns AND ensure old entries are removed + if (cooldown.isExpired()) { + cooldownConfig.set("Cooldowns." + playerUUID + "." + recipeID, null); + continue; } - } - // Save the updated cooldown data to the YML file - saveCustomYml(cooldownConfig, cooldownYml); + // Save remaining time + cooldownConfig.set("Cooldowns." + playerUUID + "." + recipeID, cooldown.getTimeLeft()); + } } + saveCustomYml(cooldownConfig, cooldownYml); } - - clear(); // Clear any additional data or cleanup + clear(); } void handleAutoDiscover() { @@ -444,7 +461,7 @@ void handleAutoDiscover() { if (!recipe.isDiscoverable() || key == null || Bukkit.getRecipe(key) == null) continue; - if (recipe.hasPerm() && !p.hasPermission(recipe.getPerm())) { + if ((recipe.hasPerm() && !p.hasPermission(recipe.getPerm())) || (!recipe.isActive())) { if (p.hasDiscoveredRecipe(key)) { p.undiscoverRecipe(key); } @@ -460,19 +477,31 @@ void handleAutoDiscover() { void addCooldowns() { if (cooldownConfig == null) return; + if (!cooldownConfig.isConfigurationSection("Cooldowns")) + return; - if (cooldownConfig.isSet("Cooldowns")) { - // Iterate through all players in the "Cooldowns" section - for (String playerUUIDString : cooldownConfig.getConfigurationSection("Cooldowns").getKeys(false)) { - UUID playerUUID = UUID.fromString(playerUUIDString); - - // Iterate through each recipe ID for this player - for (String recipeID : cooldownConfig.getConfigurationSection("Cooldowns." + playerUUIDString) - .getKeys(false)) { - // Get the cooldown time for the recipe - long cooldownTime = cooldownConfig.getLong("Cooldowns." + playerUUIDString + "." + recipeID); - cooldownManager.setCooldown(playerUUID, recipeID, cooldownTime); - } + // Loop through each player UUID section + for (String playerUUIDString : cooldownConfig.getConfigurationSection("Cooldowns").getKeys(false)) { + UUID playerUUID; + + // Validate UUID + try { + playerUUID = UUID.fromString(playerUUIDString); + } catch (IllegalArgumentException ex) { + continue; // Skip invalid UUID entries + } + + String basePath = "Cooldowns." + playerUUIDString; + + // Loop through each recipe ID inside this player's section + for (String recipeID : cooldownConfig.getConfigurationSection(basePath).getKeys(false)) { + long remainingSeconds = cooldownConfig.getLong(basePath + "." + recipeID); + if (remainingSeconds <= 0) + continue; + + // Create new cooldown with the remaining time + Cooldown cooldown = new Cooldown(recipeID, remainingSeconds); + cooldownManager.addCooldown(playerUUID, cooldown); } } } @@ -514,8 +543,14 @@ void removeRecipes() { if (s.isEmpty()) continue; - // --- First: try explicit namespaced key, e.g. "minecraft:stick" - NamespacedKey nk = NamespacedKey.fromString(s.toLowerCase()); + NamespacedKey nk; + + if (s.contains(":")) { + nk = NamespacedKey.fromString(s.toLowerCase()); + } else { + nk = NamespacedKey.minecraft(s.toLowerCase()); + } + if (nk != null) { Recipe rec = Bukkit.getRecipe(nk); if (rec == null) { @@ -750,23 +785,23 @@ && getDescription().getVersion().compareTo(newupdate) < 0) { private int[] getServerVersionParts() { String version = Bukkit.getServer().getBukkitVersion(); - // Example version: 1.14-R0.1-SNAPSHOT - int version_minor = -1; - int version_major = -1; + int major = -1, minor = -1, patch = 0; - // Split by "-" to get the version part before the hyphen String[] parts = version.split("-"); if (parts.length > 0) { - String[] version_parts = parts[0].split("\\."); // Split by dot to separate major and minor versions + String[] nums = parts[0].split("\\."); try { - version_major = Integer.parseInt(version_parts[0]); - version_minor = Integer.parseInt(version_parts[1]); + major = Integer.parseInt(nums[0]); + minor = Integer.parseInt(nums[1]); + if (nums.length > 2) { + patch = Integer.parseInt(nums[2]); + } } catch (NumberFormatException e) { Main.getInstance().getLogger().log(Level.WARNING, "Error parsing server version numbers: " + version); } } - return new int[] { version_major, version_minor }; + return new int[] { major, minor, patch }; } public boolean serverVersionAtLeast(int major, int minor) { @@ -778,6 +813,18 @@ public boolean serverVersionAtLeast(int major, int minor) { return false; } + public boolean serverVersionAtLeast(int major, int minor, int patch) { + int[] v = getServerVersionParts(); + + if (v[0] != major) + return v[0] > major; + + if (v[1] != minor) + return v[1] > minor; + + return v[2] >= patch; + } + public boolean serverVersionLessThan(int major, int minor) { int[] server_version = getServerVersionParts(); diff --git a/src/me/mehboss/recipe/RecipeManager.java b/src/me/mehboss/recipe/RecipeManager.java index 2bfc8a6..8230871 100644 --- a/src/me/mehboss/recipe/RecipeManager.java +++ b/src/me/mehboss/recipe/RecipeManager.java @@ -19,6 +19,7 @@ */ import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -27,7 +28,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.bukkit.Bukkit; -import org.bukkit.ChatColor; +import org.bukkit.Color; import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.attribute.Attribute; @@ -56,12 +57,14 @@ import org.bukkit.material.MaterialData; import org.bukkit.persistence.PersistentDataType; import org.bukkit.potion.PotionData; +import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionType; import org.bukkit.profile.PlayerProfile; import org.bukkit.profile.PlayerTextures; import com.cryptomorin.xseries.XEnchantment; import com.cryptomorin.xseries.XMaterial; +import com.cryptomorin.xseries.XPotion; import com.mojang.authlib.GameProfile; import com.mojang.authlib.properties.Property; @@ -73,9 +76,13 @@ import me.mehboss.utils.RecipeUtil.Ingredient; import me.mehboss.utils.RecipeUtil.Recipe; import me.mehboss.utils.RecipeUtil.Recipe.RecipeType; +import me.mehboss.utils.data.BrewingRecipeData; +import me.mehboss.utils.data.CookingRecipeData; +import me.mehboss.utils.data.CraftingRecipeData; +import me.mehboss.utils.data.WorkstationRecipeData; import net.advancedplugins.ae.api.AEAPI; -import valorless.havenbags.hooks.CustomRecipes; -import valorless.havenbags.hooks.CustomRecipes.BagInfo; +import valorless.havenbags.api.HavenBagsAPI; +import valorless.havenbags.datamodels.Data; public class RecipeManager { @@ -101,6 +108,33 @@ && getConfig().getString(item + ".Identifier").split("-")[0].contains("havenbags return false; } + public ItemStack handleBagCreation(Material bagMaterial, int bagSize, int bagCMD, String canBind, String bagTexture, + String item) { + + if (item != null) { + bagTexture = getConfig().isString(item + ".Bag-Texture") ? getConfig().getString(item + ".Bag-Texture") + : "none"; + canBind = getConfig().getBoolean(item + ".Can-Bind") ? "null" : "ownerless"; + bagSize = getConfig().isInt(item + ".Bag-Size") ? getConfig().getInt(item + ".Bag-Size") : 9; + bagCMD = 0; + + if (Main.getInstance().serverVersionAtLeast(1, 14) && getConfig().isSet(item + ".Custom-Model-Data") + && isInt(getConfig().getString(item + ".Custom-Model-Data"))) { + bagCMD = getConfig().getInt(item + ".Custom-Model-Data"); + } + } + + Data bagData = new Data("null", canBind); + bagData.setSize(bagSize); + bagData.setMaterial(bagMaterial); + bagData.setTexture(bagTexture); + bagData.setModeldata(bagCMD); + + ItemStack bagItem = HavenBagsAPI.generateBagItem(bagData); + bagItem = handleIdentifier(bagItem, item); + return bagItem; + } + void handleDisabledWorlds(String item, Recipe recipe) { if (getConfig().isSet(item + ".Disabled-Worlds")) { for (String world : getConfig().getStringList(item + ".Disabled-Worlds")) @@ -108,15 +142,6 @@ void handleDisabledWorlds(String item, Recipe recipe) { } } - void handleLeftovers(Material material, String item, Recipe recipe) { - if (getConfig().isSet(item + ".ItemsLeftover")) - for (String leftOver : getConfig().getStringList(item + ".ItemsLeftover")) { - if (leftOver.equalsIgnoreCase("none")) - return; - recipe.addLeftoverItem(leftOver); - } - } - void handlePlaceable(String item, Recipe recipe) { if (getConfig().isBoolean(item + ".Placeable")) recipe.setPlaceable(getConfig().getBoolean(item + ".Placeable")); @@ -142,35 +167,215 @@ void handleIgnoreFlags(String item, Recipe recipe) { } } + void handleCraftingData(Recipe recipe, String configPath) { + CraftingRecipeData workbench = (CraftingRecipeData) recipe; + if (getConfig().getBoolean(configPath + ".Use-Conditions")) { + ConfigurationSection rSec = getConfig().getConfigurationSection(configPath); + workbench.setConditionSet(RecipeConditions.parseConditionSet(rSec)); + } + + if (getConfig().isSet(configPath + ".ItemsLeftover")) + for (String leftOver : getConfig().getStringList(configPath + ".ItemsLeftover")) { + if (leftOver.equalsIgnoreCase("none")) + return; + workbench.addLeftoverItem(leftOver); + } + + if (Main.getInstance().serverVersionAtLeast(1, 13)) + if (getConfig().isSet(configPath + ".Group") + && !getConfig().getString(configPath + ".Group").equalsIgnoreCase("none")) { + try { + workbench.setGroup(getConfig().getString(configPath + ".Group")); + } catch (NoClassDefFoundError e) { + } + } + } + void handleFurnaceData(Recipe recipe, String configPath) { + CookingRecipeData furnace = (CookingRecipeData) recipe; if (getConfig().isInt(configPath + ".Cooktime")) - recipe.setCookTime(getConfig().getInt(configPath + ".Cooktime")); - + furnace.setCookTime(getConfig().getInt(configPath + ".Cooktime")); if (getConfig().isInt(configPath + ".Experience")) - recipe.setExperience(getConfig().getInt(configPath + ".Experience")); + furnace.setExperience(getConfig().getInt(configPath + ".Experience")); } void handleAnvilData(Recipe recipe, String configPath) { + WorkstationRecipeData anvil = (WorkstationRecipeData) recipe; if (getConfig().isInt(configPath + ".Repair-Cost")) - recipe.setRepairCost(getConfig().getInt(configPath + ".Repair-Cost")); + anvil.setRepairCost(getConfig().getInt(configPath + ".Repair-Cost")); } void handleGrindstoneData(Recipe recipe, String configPath) { + WorkstationRecipeData grindstone = (WorkstationRecipeData) recipe; if (getConfig().isInt(configPath + ".Experience")) - recipe.setExperience(getConfig().getInt(configPath + ".Experience")); + grindstone.setExperience(getConfig().getInt(configPath + ".Experience")); } void handleStonecutterData(Recipe recipe, String configPath) { + WorkstationRecipeData stonecutter = (WorkstationRecipeData) recipe; if (getConfig().isSet(configPath + ".Group") && !getConfig().getString(configPath + ".Group").equalsIgnoreCase("none")) - recipe.setGroup(getConfig().getString(configPath + ".Group")); + stonecutter.setGroup(getConfig().getString(configPath + ".Group")); } - void handleConditions(Recipe recipe, String configPath) { - if (getConfig().getBoolean(configPath + ".Use-Conditions")) { - ConfigurationSection rSec = getConfig().getConfigurationSection(configPath); - recipe.setConditionSet(RecipeConditions.parseConditionSet(rSec)); + void handleBrewingData(Recipe recipe, String configPath) { + BrewingRecipeData brew = (BrewingRecipeData) recipe; + String newPath = (configPath + ".Required-Item"); + + if (getConfig().getBoolean(configPath + ".Requires-Items") + && getConfig().getConfigurationSection(configPath + ".Required-Item") != null) { + ItemStack potionItem = handlePotions(newPath); + brew.setRequiredBottleType(potionItem.getType()); + brew.setRequiresBottles(true); + } + + brew.setBrewPerfect(getConfig().getBoolean(configPath + ".Exact-Choice", true)); + + if (getConfig().isSet(configPath + ".FuelSet")) { + brew.setBrewFuelSet(getConfig().getInt(configPath + ".FuelSet")); + } + + if (getConfig().isSet(configPath + ".FuelCharge")) { + brew.setBrewFuelCharge(getConfig().getInt(configPath + ".FuelCharge")); + } + } + + @SuppressWarnings("deprecation") + ItemStack handlePotions(String configPath) { + // Material + String matName = getConfig().getString(configPath + ".Material", getConfig().getString(configPath + ".Item")); + Material matType = XMaterial.matchXMaterial(matName).isPresent() ? XMaterial.matchXMaterial(matName).get().get() + : XMaterial.POTION.get(); + + boolean vanilla = getConfig().getBoolean(configPath + ".UseVanillaType", false); + Color color = parseColor(getConfig().getString(configPath + ".Color")); + + List> list = getConfig().getMapList(configPath + ".Effects"); + List effects = new ArrayList<>(); + + if (!list.isEmpty()) { + for (Map entry : list) { + String typeName = String.valueOf(entry.get("Type")); + int dur = entry.containsKey("Duration") ? Integer.parseInt(String.valueOf(entry.get("Duration"))) : 200; + int amp = entry.containsKey("Amplifier") ? Integer.parseInt(String.valueOf(entry.get("Amplifier"))) : 0; + + // XPotion lookup ONLY + Optional xp = XPotion.of(typeName); + if (!xp.isPresent() || xp.get().getPotionEffectType() == null) + continue; + + PotionEffect pe = xp.get().buildPotionEffect(dur * 20, amp + 1); + if (pe != null) + effects.add(pe); + } + } else { + String potionName = getConfig().getString(configPath + ".PotionType", "NONE"); + int durationSeconds = getConfig().getInt(configPath + ".Duration", 3600); + int amplifier = getConfig().getInt(configPath + ".Amplifier", 0); + + Optional match = XPotion.of(potionName); + if (!match.isPresent() || match.get().getPotionEffectType() == null) + return new ItemStack(Material.POTION); + + PotionEffect effect = match.get().buildPotionEffect(durationSeconds * 20, amplifier + 1); + if (effect != null) + effects.add(effect); + } + + if (effects.isEmpty()) + return new ItemStack(Material.POTION); + + if (vanilla) { + PotionEffect effect = effects.get(0); + XPotion xp = XPotion.of(effect.getType()); + + PotionType base = normalize(xp.getPotionType()); + if (base == null) { + Bukkit.getLogger().warning("[DEBUG] Invalid vanilla potion type '" + xp + "'. Falling back to custom."); + return XPotion.buildItemWithEffects(matType, color, effects.toArray(new PotionEffect[0])); + } + + ItemStack item = new ItemStack(matType); + PotionMeta meta = (PotionMeta) item.getItemMeta(); + + boolean isExtended = effect.getDuration() > (3600 * 20); + boolean isUpgraded = effect.getAmplifier() > 0; + + try { + meta.setBasePotionData(new PotionData(base, isExtended, isUpgraded)); + } catch (Throwable ignored) { + } + + if (color != null) + meta.setColor(color); + + item.setItemMeta(meta); + return item; + } + return XPotion.buildItemWithEffects(matType, color, effects.toArray(new PotionEffect[0])); + } + + private PotionType normalize(PotionType type) { + if (type == null) + return null; + if (!Main.getInstance().serverVersionAtLeast(1, 20)) + return type; + // New API since 1.20.5 only accepts base types. + try { + return PotionType.valueOf(type.name().replace("LONG_", "").replace("STRONG_", "")); + } catch (IllegalArgumentException ex) { + return null; + } + } + + /** + * Parses a color string like "RED", "BLUE", or "255,0,0" to a Bukkit Color. + */ + private Color parseColor(String input) { + if (input == null) + return null; + + try { + // Try named colors first (RED, BLUE, GREEN, etc.) + switch (input.toUpperCase()) { + case "RED": + return Color.RED; + case "BLUE": + return Color.BLUE; + case "GREEN": + return Color.GREEN; + case "YELLOW": + return Color.YELLOW; + case "PURPLE": + return Color.PURPLE; + case "AQUA": + return Color.AQUA; + case "WHITE": + return Color.WHITE; + case "BLACK": + return Color.BLACK; + case "FUCHSIA": + return Color.FUCHSIA; + case "ORANGE": + return Color.ORANGE; + case "GRAY": + return Color.GRAY; + } + + // Otherwise, try parsing RGB manually (e.g., "255,0,0") + if (input.contains(",")) { + String[] parts = input.split(","); + if (parts.length == 3) { + int r = Integer.parseInt(parts[0].trim()); + int g = Integer.parseInt(parts[1].trim()); + int b = Integer.parseInt(parts[2].trim()); + return Color.fromRGB(r, g, b); + } + } + } catch (Exception ignored) { } + + return null; } ItemStack handleHeadTexture(String material) { @@ -287,7 +492,7 @@ ItemStack applyCustomTags(ItemStack item, String recipe) { return potion; } else if (path.get(0).equalsIgnoreCase("Enchantments")) { // Check if it's an enchantment book - if (item.getType() == Material.ENCHANTED_BOOK) { + if (item.getType() == XMaterial.ENCHANTED_BOOK.get()) { Map enchants = (Map) value; // Get the EnchantmentMeta to apply enchantments to the enchanted book @@ -308,7 +513,7 @@ ItemStack applyCustomTags(ItemStack item, String recipe) { // Apply the modified enchantment meta to the item item.setItemMeta(meta); } - } else if ((item.getType() == Material.ITEM_FRAME || item.getType() == Material.GLOW_ITEM_FRAME) + } else if ((item.getType() == XMaterial.ITEM_FRAME.get() || item.getType() == XMaterial.GLOW_ITEM_FRAME.get()) && path.get(0).equalsIgnoreCase("EntityTag")) { try { @@ -425,32 +630,11 @@ List> getCustomTags(String recipe) { return castedList; } - ItemStack handleBagCreation(Material bagMaterial, String item) { - String bagTexture = getConfig().isString(item + ".Bag-Texture") ? getConfig().getString(item + ".Bag-Texture") - : "none"; - boolean canBind = getConfig().isBoolean(item + ".Can-Bind") ? getConfig().getBoolean(item + ".Can-Bind") : true; - int bagSize = getConfig().isInt(item + ".Bag-Size") ? getConfig().getInt(item + ".Bag-Size") : 1; - int bagCMD = 0; - - CustomRecipes havenBags = new CustomRecipes(); - BagInfo bInfo = havenBags.new BagInfo(null, bagMaterial, bagSize, canBind, bagTexture); - - if (Main.getInstance().serverVersionAtLeast(1, 14) && getConfig().isSet(item + ".Custom-Model-Data") - && isInt(getConfig().getString(item + ".Custom-Model-Data"))) { - bagCMD = getConfig().getInt(item + ".Custom-Model-Data"); - } - - bInfo.setModelData(bagCMD); - - ItemStack bag = CustomRecipes.CreateBag(bInfo); - return bag; - } - ItemStack handleIdentifier(ItemStack i, String item) { if (!getConfig().isSet(item + ".Identifier")) return i; - String identifier = getConfig().getString(item + ".Identifier"); + String identifier = getConfig().getString(item.replaceAll(".Result", "") + ".Identifier"); if (getConfig().getBoolean(item + ".Custom-Tagged")) i = NBTEditor.set(i, identifier, NBTEditor.CUSTOM_DATA, "CUSTOM_ITEM_IDENTIFIER"); @@ -467,7 +651,8 @@ ItemStack handleIdentifier(ItemStack i, String item) { public ItemStack handleDurability(ItemStack i, String item) { if (getConfig().isSet(item + ".Durability")) { String durability = getConfig().getString(item + ".Durability"); - if (!durability.equals("100") && !durability.equals("0")) + if (!durability.isEmpty() && !durability.equals("100") && !durability.equals("0") + && !durability.equals("none")) i.setDurability(Short.valueOf(getConfig().getString(item + ".Durability"))); } return i; @@ -572,7 +757,7 @@ ItemMeta handleDisplayname(String item, ItemStack recipe) { logDebug("Applying displayname..", item); logDebug("Displayname: " + name, item); - itemMeta.setDisplayName(ChatColor.translateAlternateColorCodes('&', name)); + itemMeta = CompatibilityUtil.setDisplayname(recipe, name); } return itemMeta; } @@ -674,16 +859,38 @@ void handleCommand(String item, Recipe recipe) { logDebug("Successfully set commands: " + commands, item); } - private void checkIdentifiers() { + void handleMaterialChoice(Recipe recipe, Ingredient ingredient, String path) { + List list = getConfig().getStringList(path + ".Materials"); + if (list == null || list.isEmpty()) + return; + for (String material : list) { + Optional rawMaterial = XMaterial.matchXMaterial(material); + if (!validMaterial(recipe.getName(), material, rawMaterial)) + continue; + ingredient.addMaterialChoice(rawMaterial.get().get()); + } + } + + void checkIdentifiers() { for (Recipe recipe : new ArrayList<>(getRecipeUtil().getAllRecipes().values())) { for (Ingredient ingredient : recipe.getIngredients()) { - if (ingredient.hasIdentifier() - && getRecipeUtil().getResultFromKey(ingredient.getIdentifier() + ":" + recipe.getName()) == null) { + if (!ingredient.hasIdentifier()) + continue; + + // recipe name hyphen-argument is strictly for debugging, and is needed for + // attaching a recipe during getResultFromKey() + ItemStack matchedItem = getRecipeUtil() + .getResultFromKey(ingredient.getIdentifier() + ":" + recipe.getName()); + if (matchedItem == null) { logError("Please double check the IDs of the ingredients matches that of a custom item or recipe.", recipe.getName()); logError("Skipping recipe..", recipe.getName()); getRecipeUtil().removeRecipe(recipe.getName()); break; + } else { + if (ingredient.getMaterial() != matchedItem.getType()) { + ingredient.setMaterial(matchedItem.getType()); + } } } } @@ -702,6 +909,10 @@ public void addRecipes(String name) { } if (name != null) { + if (!name.endsWith(".yml")) { + name = name + ".yml"; + } + File single = new File(recipeFolder, name); if (single.exists() && single.isFile()) { recipeFiles = new File[] { single }; @@ -722,7 +933,7 @@ public void addRecipes(String name) { continue; } - Recipe recipe = new Recipe(item); + Recipe recipe; List gridRows = getConfig().getStringList(item + ".ItemCrafting"); String converter = getConfig().isString(item + ".Converter") @@ -730,84 +941,75 @@ public void addRecipes(String name) { : "converterNotDefined"; int amountRequirement = 9; - logDebug("Attempting to add recipe..", recipe.getName()); - - if (!(hasHavenBag()) && isHavenBag(item)) { - logError("Error loading recipe..", recipe.getName()); - logError("Found a havenbag recipe, but can not find the havenbags plugin. Skipping recipe..", - recipe.getName()); - continue; - } + logDebug("Attempting to add recipe..", item); if (!getConfig().isConfigurationSection(item + ".Ingredients")) { - logError("Error loading recipe..", recipe.getName()); + logError("Error loading recipe..", item); logError("Could not locate the ingredients section. Please double check formatting. Skipping recipe..", - recipe.getName()); + item); continue; } - switch (converter) { - case "stonecutter": - recipe.setType(RecipeType.STONECUTTER); + RecipeType type = RecipeType.fromString(converter); + switch (type) { + case STONECUTTER: + recipe = new WorkstationRecipeData(item); + recipe.setType(type); handleStonecutterData(recipe, item); amountRequirement = 1; break; - case "furnace": - recipe.setType(RecipeType.FURNACE); + case FURNACE: + recipe = new CookingRecipeData(item); + recipe.setType(type); handleFurnaceData(recipe, item); amountRequirement = 2; break; - case "blastfurnace": - recipe.setType(RecipeType.BLASTFURNACE); + case BLASTFURNACE: + recipe = new CookingRecipeData(item); + recipe.setType(type); handleFurnaceData(recipe, item); amountRequirement = 2; break; - case "smoker": - recipe.setType(RecipeType.SMOKER); + case SMOKER: + recipe = new CookingRecipeData(item); + recipe.setType(type); handleFurnaceData(recipe, item); amountRequirement = 2; break; - case "campfire": - recipe.setType(RecipeType.CAMPFIRE); + case CAMPFIRE: + recipe = new CookingRecipeData(item); + recipe.setType(type); handleFurnaceData(recipe, item); amountRequirement = 1; break; - case "anvil": - recipe.setType(RecipeType.ANVIL); + case ANVIL: + recipe = new WorkstationRecipeData(item); + recipe.setType(type); handleAnvilData(recipe, item); amountRequirement = 2; break; - case "grindstone": - recipe.setType(RecipeType.GRINDSTONE); + case GRINDSTONE: + recipe = new WorkstationRecipeData(item); + recipe.setType(type); handleGrindstoneData(recipe, item); amountRequirement = 2; break; - + case BREWING_STAND: + recipe = new BrewingRecipeData(item); + recipe.setType(type); + handleBrewingData(recipe, item); + amountRequirement = 2; + break; default: - if (getConfig().isBoolean(item + ".Shapeless") && getConfig().getBoolean(item + ".Shapeless")) { - recipe.setType(RecipeType.SHAPELESS); - break; - } else { - recipe.setType(RecipeType.SHAPED); - break; - } - } - - if (!Main.getInstance().serverVersionAtLeast(1, 14) - && (recipe.getType() == RecipeType.STONECUTTER || recipe.getType() == RecipeType.BLASTFURNACE - || recipe.getType() == RecipeType.SMOKER || recipe.getType() == RecipeType.CAMPFIRE)) { - logError("Error loading recipe..", recipe.getName()); - logError("Found recipe type " + converter + ", but your server version is below 1.14.", - recipe.getName()); - continue; + recipe = new CraftingRecipeData(item); + recipe.setType(getConfig().getBoolean(item + ".Shapeless") ? RecipeType.SHAPELESS : RecipeType.SHAPED); + handleCraftingData(recipe, item); + break; } - if (!Main.getInstance().serverVersionAtLeast(1, 16) && (recipe.getType() == RecipeType.GRINDSTONE)) { - logError("Error loading recipe..", recipe.getName()); - logError("Found recipe type " + converter + ", but your server version is below 1.16.", - recipe.getName()); + // Checks version req. of recipe types + if (!isVersionSupported(recipe.getType(), item)) continue; - } // HavenBag detected, but converter is not SHAPED or SHAPELESS if (recipe.getType() != RecipeType.SHAPED && recipe.getType() != RecipeType.SHAPELESS) { @@ -819,18 +1021,21 @@ public void addRecipes(String name) { } if (name == null) { - delayedRecipes.add(recipeFile.getName()); + delayedRecipes.add(item); continue; } } - int amount = getConfig().isInt(item + ".Amount") ? getConfig().getInt(item + ".Amount") : 1; + String resultPath = getConfig().isConfigurationSection(item + ".Result") ? item + ".Result" : item; + int amount = getConfig().isInt(resultPath + ".Amount") ? getConfig().getInt(resultPath + ".Amount") : 1; String identifier = getConfig().getString(item + ".Identifier"); recipe.setKey(identifier); // Checks for a custom item and attempts to set it - String rawItem = getConfig().getString(item + ".Item") != null ? getConfig().getString(item + ".Item") + String rawItem = getConfig().getString(resultPath + ".Item") != null + ? getConfig().getString(resultPath + ".Item") : null; + // Attach recipe name ONLY for debug purposes ItemStack i = getRecipeUtil().getResultFromKey(rawItem + ":" + recipe.getName()); // handle custom item stacks @@ -838,12 +1043,12 @@ public void addRecipes(String name) { recipe.setCustomItem(rawItem); if (i == null) { - if (getConfig().getItemStack(item + ".Item") != null) { + if (getConfig().getItemStack(resultPath + ".Item") != null) { // handle itemstack checks - i = getConfig().getItemStack(item + ".Item"); + i = getConfig().getItemStack(resultPath + ".Item"); } else { // handle material checks - Optional built = buildItem(item, getConfig()); + Optional built = buildItem(resultPath, getConfig()); if (!built.isPresent()) continue; @@ -851,20 +1056,15 @@ public void addRecipes(String name) { } } - i = handleDurability(i, item); + i = handleDurability(i, resultPath); i.setAmount(amount); - if (isHavenBag(item)) - i = handleBagCreation(i.getType(), item); - recipe.setResult(i); handleIgnoreFlags(item, recipe); handleCooldown(item, recipe); handlePlaceable(item, recipe); handleCommand(item, recipe); - handleLeftovers(i.getType(), item, recipe); handleDisabledWorlds(item, recipe); - handleConditions(recipe, item); if (getConfig().getBoolean(item + ".Custom-Tagged")) recipe.setTagged(true); @@ -910,13 +1110,14 @@ public void addRecipes(String name) { count++; String configPath = item + ".Ingredients." + abbreviation; - String material = getConfig().getString(configPath + ".Material"); + List list = getConfig().getStringList(configPath + ".Materials"); + String material = !list.isEmpty() ? list.get(0) : getConfig().getString(configPath + ".Material"); Optional rawMaterial = XMaterial.matchXMaterial(material); if (!validMaterial(recipe.getName(), material, rawMaterial)) { continue recipeLoop; } - Material ingredientMaterial = rawMaterial.get().parseMaterial(); + Material ingredientMaterial = rawMaterial.get().get(); if (count > amountRequirement) { logError("Error loading recipe..", recipe.getName()); logError( @@ -935,8 +1136,8 @@ public void addRecipes(String name) { int ingredientAmount = getConfig().isInt(configPath + ".Amount") ? getConfig().getInt(configPath + ".Amount") : 1; - int ingredientCMD = getConfig().isInt(configPath + ".CustomModelData") - ? getConfig().getInt(configPath + ".CustomModelData") + int ingredientCMD = getConfig().isInt(configPath + ".Custom-Model-Data") + ? getConfig().getInt(configPath + ".Custom-Model-Data") : -1; logDebug("Ingredient Name: " + ingredientName, recipe.getName()); @@ -951,6 +1152,9 @@ public void addRecipes(String name) { recipeIngredient.setAmount(ingredientAmount); recipeIngredient.setSlot(slot); + // handles material choice inputs for ingredients + handleMaterialChoice(recipe, recipeIngredient, configPath); + if (rawMaterial.get().getData() != 0) { logDebug("[LegacyID] Found legacy ID - " + ingredientMaterial + ", with a short value of " + rawMaterial.get().getData(), recipe.getName()); @@ -973,17 +1177,22 @@ public void addRecipes(String name) { recipe.setPerm(getConfig().getString(item + ".Permission")); getRecipeUtil().createRecipe(recipe); + if (delayedRecipes.isEmpty() && name != null) { + getRecipeUtil().registerRecipe(recipe); + return; + } } - if (name == null && !delayedRecipes.isEmpty()) { + if (!delayedRecipes.isEmpty() && name == null) { for (String recipe : delayedRecipes) addRecipes(recipe); delayedRecipes.clear(); } - if (delayedRecipes.isEmpty()) + if (delayedRecipes.isEmpty()) { getRecipeUtil().reloadRecipes(); + } } boolean hasItemDamage(String item, Optional type) { @@ -1023,13 +1232,33 @@ public Optional buildItem(String item, FileConfiguration path) { if (!(validMaterial(item, path.getString(item + ".Item"), type))) return Optional.empty(); - ItemStack i = hasItemDamage(item, type) ? handleItemDamage(item, type) - : new ItemStack(type.get().parseMaterial(), 1); + ItemStack i = hasItemDamage(item, type) ? handleItemDamage(item, type) : new ItemStack(type.get().get(), 1); ItemMeta m = i.getItemMeta(); + if (isHavenBag(item)) { + if (!(hasHavenBag())) { + logError("Error loading recipe..", item); + logError("Found a havenbag recipe, but no havenbags plugin found. Skipping recipe..", item); + return Optional.empty(); + } + return Optional.of(handleBagCreation(i.getType(), 0, 0, "null", null, item)); + } + // handle head textures - if (handleHeadTexture(path.getString(item + ".Item")) != null) - i = handleHeadTexture(path.getString(item + ".Item")); + ItemStack texture = handleHeadTexture(path.getString(item + ".Item")); + if (texture != null) { + i = texture; + } + + // handle potions + if (type.get() == XMaterial.POTION) { + if (Main.getInstance().serverVersionLessThan(1, 9)) { + logError("Error loading recipe..", item); + logError("Potions are only supported on > 1.9! Skipping recipe..", item); + return Optional.empty(); + } + i = handlePotions(item); + } i = handleEnchants(i, item); i = handleCustomEnchants(i, item); @@ -1049,15 +1278,15 @@ public Optional buildItem(String item, FileConfiguration path) { public void addRecipesFromAPI(Recipe specificRecipe) { RecipeUtil recipeUtil = Main.getInstance().recipeUtil; - HashMap recipeList = recipeUtil.getAllRecipes(); + HashMap recipeList = recipeUtil.getAllRecipes() == null ? null + : new HashMap<>(recipeUtil.getAllRecipes()); - if (recipeList == null) { + if (recipeList == null || recipeList.isEmpty()) { logError("No recipes were found to load..", ""); return; } if (specificRecipe != null) { - if (recipeList.containsKey(specificRecipe.getName())) - recipeList.remove(specificRecipe.getName()); + recipeList.clear(); recipeList.put(specificRecipe.getName(), specificRecipe); } @@ -1075,55 +1304,62 @@ public void addRecipesFromAPI(Recipe specificRecipe) { switch (recipe.getType()) { case SHAPELESS: if (Main.getInstance().serverVersionAtLeast(1, 12)) { - shapelessRecipe = Main.getInstance().exactChoice.createShapelessRecipe(recipe); + shapelessRecipe = Main.getInstance().exactChoice + .createShapelessRecipe((CraftingRecipeData) recipe); break; } - shapelessRecipe = createShapelessRecipe(recipe); + shapelessRecipe = createShapelessRecipe((CraftingRecipeData) recipe); break; case SHAPED: if (Main.getInstance().serverVersionAtLeast(1, 12)) { - shapedRecipe = Main.getInstance().exactChoice.createShapedRecipe(recipe); + shapedRecipe = Main.getInstance().exactChoice.createShapedRecipe((CraftingRecipeData) recipe); break; } - shapedRecipe = createShapedRecipe(recipe); + shapedRecipe = createShapedRecipe((CraftingRecipeData) recipe); break; case FURNACE: if (Main.getInstance().serverVersionAtLeast(1, 12)) { - furnaceRecipe = Main.getInstance().exactChoice.createFurnaceRecipe(recipe); + furnaceRecipe = Main.getInstance().exactChoice.createFurnaceRecipe((CookingRecipeData) recipe); break; } - furnaceRecipe = createFurnaceRecipe(recipe); + furnaceRecipe = createFurnaceRecipe((CookingRecipeData) recipe); break; case BLASTFURNACE: if (Main.getInstance().serverVersionAtLeast(1, 12)) { - blastRecipe = Main.getInstance().exactChoice.createBlastFurnaceRecipe(recipe); + blastRecipe = Main.getInstance().exactChoice + .createBlastFurnaceRecipe((CookingRecipeData) recipe); } break; case SMOKER: if (Main.getInstance().serverVersionAtLeast(1, 12)) { - smokerRecipe = Main.getInstance().exactChoice.createSmokerRecipe(recipe); + smokerRecipe = Main.getInstance().exactChoice.createSmokerRecipe((CookingRecipeData) recipe); } break; case STONECUTTER: if (Main.getInstance().serverVersionAtLeast(1, 12)) { - sCutterRecipe = Main.getInstance().exactChoice.createStonecuttingRecipe(recipe); + sCutterRecipe = Main.getInstance().exactChoice + .createStonecuttingRecipe((WorkstationRecipeData) recipe); } break; case CAMPFIRE: if (Main.getInstance().serverVersionAtLeast(1, 12)) { - campfireRecipe = Main.getInstance().exactChoice.createCampfireRecipe(recipe); + campfireRecipe = Main.getInstance().exactChoice + .createCampfireRecipe((CookingRecipeData) recipe); } break; + case BREWING_STAND: + setBrewingItems((BrewingRecipeData) recipe); + break; default: break; } @@ -1153,7 +1389,7 @@ public void addRecipesFromAPI(Recipe specificRecipe) { } @SuppressWarnings("deprecation") - private ShapelessRecipe createShapelessRecipe(Recipe recipe) { + private ShapelessRecipe createShapelessRecipe(CraftingRecipeData recipe) { ShapelessRecipe shapelessRecipe; if (Main.getInstance().serverVersionAtLeast(1, 12)) { @@ -1178,7 +1414,7 @@ private ShapelessRecipe createShapelessRecipe(Recipe recipe) { } @SuppressWarnings("deprecation") - private ShapedRecipe createShapedRecipe(Recipe recipe) { + private ShapedRecipe createShapedRecipe(CraftingRecipeData recipe) { ShapedRecipe shapedRecipe; ArrayList ingredients = new ArrayList(); @@ -1209,7 +1445,7 @@ private ShapedRecipe createShapedRecipe(Recipe recipe) { } @SuppressWarnings("deprecation") - private FurnaceRecipe createFurnaceRecipe(Recipe recipe) { + private FurnaceRecipe createFurnaceRecipe(CookingRecipeData recipe) { FurnaceRecipe furnaceRecipe; @@ -1229,7 +1465,7 @@ NamespacedKey createNamespacedKey(Recipe recipe) { } private boolean validMaterial(String recipe, String materialInput, Optional type) { - if (type == null || !type.isPresent()) { + if (type == null || !type.isPresent() || type.get().get() == null) { if (isCustomItem(materialInput)) return false; @@ -1242,7 +1478,12 @@ private boolean validMaterial(String recipe, String materialInput, Optional v16_Types = Arrays.asList(RecipeType.GRINDSTONE); + List legacyTypes = Arrays.asList(RecipeType.ANVIL, RecipeType.SHAPED, RecipeType.SHAPELESS); + + if (Main.getInstance().serverVersionLessThan(1, 14) && !legacyTypes.contains(type)) { + logError("Error loading recipe..", item); + logError(">= 1.14 is required for " + type.toString() + " recipes!", item); + return false; + } + if (Main.getInstance().serverVersionLessThan(1, 16) && v16_Types.contains(type)) { + logError("Error loading recipe..", item); + logError(">= 1.16 is required for " + type.toString() + " recipes!", item); + return false; + } + return true; + } + boolean isCustomItem(String id) { String[] key = id.split(":"); if (key.length < 2) @@ -1294,7 +1552,7 @@ private void logError(String st, String recipe) { private void logDebug(String st, String recipe) { if (Main.getInstance().debug) Logger.getLogger("Minecraft").log(Level.WARNING, - "[DEBUG][" + Main.getInstance().getName() + "][" + recipe + "] " + st); + "[DEBUG][" + Main.getInstance().getName() + "][" + recipe.replaceAll(".Result", "") + "] " + st); } private FileConfiguration getConfig() { diff --git a/src/me/mehboss/recipe/package-info.java b/src/me/mehboss/recipe/package-info.java new file mode 100644 index 0000000..b5096e0 --- /dev/null +++ b/src/me/mehboss/recipe/package-info.java @@ -0,0 +1,8 @@ +/** + * Core recipe management system. + *

    + * Contains the main plugin initialization, recipe storage, loading, + * registration, and the central RecipeUtil class used throughout + * the plugin to resolve ingredients and results. + */ +package me.mehboss.recipe; \ No newline at end of file diff --git a/src/me/mehboss/utils/CompatibilityUtil.java b/src/me/mehboss/utils/CompatibilityUtil.java index db18627..973f3b9 100644 --- a/src/me/mehboss/utils/CompatibilityUtil.java +++ b/src/me/mehboss/utils/CompatibilityUtil.java @@ -3,115 +3,208 @@ import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.Arrays; import java.util.Base64; +import org.bukkit.ChatColor; import org.bukkit.entity.Player; import org.bukkit.event.inventory.InventoryEvent; import org.bukkit.inventory.Inventory; -import org.json.JSONObject; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import com.cryptomorin.xseries.XMaterial; +import com.eclipsesource.json.Json; +import com.eclipsesource.json.JsonObject; +import me.mehboss.recipe.Main; + +/** + * Utility class providing cross-version compatibility helpers that rely on + * reflection to handle differences between Minecraft / Bukkit versions. + *

    + * This includes: + *

      + *
    • Accessing InventoryView methods safely across versions
    • + *
    • Retrieving players, titles, and top inventories without relying on version-specific classes
    • + *
    • Reflection helpers for invoking private methods and fields
    • + *
    • Base64 decoding for skin texture analysis
    • + *
    • Item name getters/setters supporting 1.20.5+ name changes
    • + *
    + */ public class CompatibilityUtil { - /** - * In API versions 1.20.6 and earlier, InventoryView is a class. - * In versions 1.21 and later, it is an interface. - * This method uses reflection to get the top Inventory object from the - * InventoryView associated with an InventoryEvent, to avoid runtime errors. - * @param event The generic InventoryEvent with an InventoryView to inspect. - * @return The top Inventory object from the event's InventoryView. - */ - + + /** + * Retrieves the InventoryView from an {@link InventoryEvent} using reflection. + *

    + * Different server versions may change how InventoryView is exposed, + * so this reflective access ensures compatibility. + * + * @param event The inventory event. + * @return The InventoryView object (returned as {@code Object} for compatibility). + */ + public static Object getInventoryView(InventoryEvent event) { + try { + Method getViewMethod = InventoryEvent.class.getMethod("getView"); + return getViewMethod.invoke(event); // returns Object (not InventoryView!) + } catch (Exception e) { + throw new RuntimeException("Failed to get InventoryView reflectively", e); + } + } + /** - * Gets the InventoryView from an InventoryEvent using reflection to avoid - * class/interface compatibility issues across versions (e.g. 1.20 vs 1.21+). + * Extracts the {@link Player} from an InventoryView instance using reflection. * - * @param event The inventory event - * @return The InventoryView associated with the event + * @param view The InventoryView object. + * @return The player who opened the inventory, or {@code null} if unavailable. + */ + public static Player getPlayerFromView(Object view) { + try { + Method getPlayerMethod = view.getClass().getMethod("getPlayer"); + Object result = getPlayerMethod.invoke(view); + if (result instanceof Player) { + return (Player) result; + } + return null; + } catch (Exception e) { + throw new RuntimeException("Failed to get Player from InventoryView reflectively", e); + } + } + + /** + * Retrieves the top inventory from an {@link InventoryEvent} using reflection. + * + * @param event The inventory event. + * @return The top inventory (e.g. chest, crafting grid). */ - public static Object getInventoryView(InventoryEvent event) { - try { - Method getViewMethod = InventoryEvent.class.getMethod("getView"); - return getViewMethod.invoke(event); // returns Object (not InventoryView!) - } catch (Exception e) { - throw new RuntimeException("Failed to get InventoryView reflectively", e); - } - } - - public static Player getPlayerFromView(Object view) { - try { - Method getPlayerMethod = view.getClass().getMethod("getPlayer"); - Object result = getPlayerMethod.invoke(view); - if (result instanceof Player) { - return (Player) result; - } - return null; - } catch (Exception e) { - throw new RuntimeException("Failed to get Player from InventoryView reflectively", e); - } - } - public static Inventory getTopInventory(InventoryEvent event) { - try { - Object view = event.getView(); - Method getTopInventory = view.getClass().getMethod("getTopInventory"); - getTopInventory.setAccessible(true); - return (Inventory) getTopInventory.invoke(view); - } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { - throw new RuntimeException(e); - } + try { + Object view = event.getView(); + Method getTopInventory = view.getClass().getMethod("getTopInventory"); + getTopInventory.setAccessible(true); + return (Inventory) getTopInventory.invoke(view); + } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + /** + * Retrieves an inventory's title reflectively. + *

    + * Some Bukkit versions moved {@code getTitle()} into subclasses, so this + * method uses superclass traversal for compatibility. + * + * @param event The inventory event. + * @return Title of the inventory window. + */ + public static String getTitle(InventoryEvent event) { + try { + Object view = event.getView(); // InventoryView + Class inventoryViewClass = view.getClass().getSuperclass(); // Go one level up to avoid anonymous class + Method getTitle = inventoryViewClass.getMethod("getTitle"); + return (String) getTitle.invoke(view); + } catch (Exception e) { + throw new RuntimeException("Failed to get inventory title", e); + } + } + + public static Method getMethod(Class clazz, String name, Class... parameterTypes) { + try { + Method method = clazz.getDeclaredMethod(name, parameterTypes); + method.setAccessible(true); + return method; + } catch (NoSuchMethodException e) { + e.printStackTrace(); + return null; + } + } + + public static void invokeMethod(Method method, Object instance, Object... args) { + try { + method.invoke(instance, args); + } catch (IllegalAccessException | InvocationTargetException e) { + e.printStackTrace(); + } + } + + public static void setFieldValue(Object instance, String fieldName, Object value) { + try { + Field field = instance.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + field.set(instance, value); + } catch (NoSuchFieldException | IllegalAccessException e) { + e.printStackTrace(); + } + } + + /** + * Extracts the skin texture URL from a Base64-encoded JSON texture payload. + * + * @param base64Texture A Base64-encoded Mojang skin texture string. + * @return The texture URL inside the JSON. + */ + public static String extractUrlFromBase64(String base64Texture) { + byte[] decodedBytes = Base64.getDecoder().decode(base64Texture); + String json = new String(decodedBytes); + + JsonObject obj = Json.parse(json).asObject(); + + return obj.get("textures") + .asObject() + .get("SKIN") + .asObject() + .getString("url", null); + } + + /** + * Checks if an item has a display name. Supports Bukkit name API changes + * introduced in Minecraft 1.20.5. + * + * @param item ItemMeta to inspect. + * @return True if the item has a custom name. + */ + public static boolean hasDisplayname(ItemMeta item) { + if (Main.getInstance().serverVersionAtLeast(1, 20, 5)) + return item.hasItemName(); + + return item.hasDisplayName(); + } + + /** + * Retrieves the display name from an item. + * + * @param item ItemMeta from which to read the name. + * @return The custom display name. + */ + public static String getDisplayname(ItemMeta item) { + if (Main.getInstance().serverVersionAtLeast(1, 20, 5)) + return item.getItemName(); + + return item.getDisplayName(); + } + + /** + * Sets an item's display name using version-aware APIs. + *

    + * In 1.20.5+: + *

      + *
    • New naming system applies to all items except potion variants.
    • + *
    + * + * @param item The item whose meta should be modified. + * @param name The new display name (supports color codes). + * @return The updated ItemMeta with the new name applied. + */ + public static ItemMeta setDisplayname(ItemStack item, String name) { + XMaterial[] itemTypes = { XMaterial.POTION, XMaterial.LINGERING_POTION, XMaterial.SPLASH_POTION }; + XMaterial itemMaterial = XMaterial.matchXMaterial(item.getType()); + ItemMeta itemM = item.getItemMeta(); + + if (Main.getInstance().serverVersionAtLeast(1, 20, 5) && !Arrays.asList(itemTypes).contains(itemMaterial)) { + itemM.setItemName(ChatColor.translateAlternateColorCodes('&', name)); + return itemM; + } + + itemM.setDisplayName(ChatColor.RESET + ChatColor.translateAlternateColorCodes('&', name)); + return itemM; } - - public static String getTitle(InventoryEvent event) { - try { - Object view = event.getView(); // InventoryView - Class inventoryViewClass = view.getClass().getSuperclass(); // Go one level up to avoid anonymous class - Method getTitle = inventoryViewClass.getMethod("getTitle"); - return (String) getTitle.invoke(view); - } catch (Exception e) { - throw new RuntimeException("Failed to get inventory title", e); - } - } - - public static Method getMethod(Class clazz, String name, Class... parameterTypes) { - try { - Method method = clazz.getDeclaredMethod(name, parameterTypes); - method.setAccessible(true); - return method; - } catch (NoSuchMethodException e) { - e.printStackTrace(); - return null; - } - } - - public static void invokeMethod(Method method, Object instance, Object... args) { - try { - method.invoke(instance, args); - } catch (IllegalAccessException | InvocationTargetException e) { - e.printStackTrace(); - } - } - - public static void setFieldValue(Object instance, String fieldName, Object value) { - try { - Field field = instance.getClass().getDeclaredField(fieldName); - field.setAccessible(true); - field.set(instance, value); - } catch (NoSuchFieldException | IllegalAccessException e) { - e.printStackTrace(); - } - } - - public static String extractUrlFromBase64(String base64Texture) { - // Decode the Base64 string back into the original JSON string - byte[] decodedBytes = Base64.getDecoder().decode(base64Texture); - String json = new String(decodedBytes); - - // Parse the JSON string to extract the texture URL - JSONObject jsonObj = new JSONObject(json); - String textureUrl = jsonObj - .getJSONObject("textures") - .getJSONObject("SKIN") - .getString("url"); - - return textureUrl; - } } \ No newline at end of file diff --git a/src/me/mehboss/utils/CooldownManager.java b/src/me/mehboss/utils/CooldownManager.java new file mode 100644 index 0000000..813d888 --- /dev/null +++ b/src/me/mehboss/utils/CooldownManager.java @@ -0,0 +1,203 @@ +package me.mehboss.utils; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * Manages cooldown timers for players on a per-recipe basis. + *

    + * This class tracks cooldowns using a nested map structure: + *

    + *   Player UUID → (Recipe ID → Cooldown instance)
    + * 
    + * + * It supports: + *
      + *
    • Adding cooldowns
    • + *
    • Checking active cooldowns
    • + *
    • Auto-removing expired cooldowns
    • + *
    • Saving and restoring cooldown data
    • + *
    • Querying remaining cooldown time
    • + *
    + * + * Cooldowns are represented by the {@link Cooldown} inner class. + */ +public class CooldownManager { + + private final Map> cooldowns = new HashMap<>(); + + /** + * Adds a cooldown for a specific player. + * + * @param player The player UUID. + * @param cd The cooldown to apply. + */ + public void addCooldown(UUID player, Cooldown cd) { + cooldowns + .computeIfAbsent(player, k -> new HashMap<>()) + .put(cd.getRecipeID(), cd); + } + + /** + * Retrieves a cooldown for a player and recipe. + * + * @param player The player UUID. + * @param recipeID The ID of the recipe. + * @return The cooldown, or {@code null} if none exists. + */ + public Cooldown getCooldown(UUID player, String recipeID) { + return cooldowns + .getOrDefault(player, Collections.emptyMap()) + .get(recipeID); + } + + /** + * Checks whether a cooldown is active for the given player and recipe. + *

    + * This method automatically removes expired cooldowns. + * + * @param player The player UUID. + * @param recipeID The recipe ID. + * @return {@code true} if the cooldown exists and is still active. + */ + public boolean hasCooldown(UUID player, String recipeID) { + Cooldown cd = getCooldown(player, recipeID); + + // No cooldown at all + if (cd == null) return false; + + // Expired? Remove it automatically + if (cd.isExpired()) { + removeCooldown(player, recipeID); + return false; + } + + // Still active + return true; + } + + /** + * Removes a cooldown for a specific recipe and player. + * + * @param player The player UUID. + * @param recipeID The recipe ID to remove. + */ + public void removeCooldown(UUID player, String recipeID) { + Map map = cooldowns.get(player); + if (map != null) map.remove(recipeID); + } + + /** + * Gets the number of seconds left on a cooldown. + *

    + * Automatically clears expired cooldowns. + * + * @param player The player UUID. + * @param recipeID The recipe ID. + * @return Seconds remaining, or {@code 0} if expired or not found. + */ + public long getTimeLeft(UUID player, String recipeID) { + Cooldown cd = getCooldown(player, recipeID); + if (cd == null) return 0; + if (cd.isExpired()) { + removeCooldown(player, recipeID); + return 0; + } + return cd.getTimeLeft(); + } + + /** + * Cleans all expired cooldowns from memory. + *

    + * Useful for periodic cleanup tasks. + */ + public void cleanExpired() { + for (UUID player : cooldowns.keySet()) { + cooldowns.get(player).values().removeIf(Cooldown::isExpired); + } + } + + // Get full map for saving + public Map> getCooldowns() { + return cooldowns; + } + + /** + * Restores cooldowns from a saved map. + *

    + * Existing cooldowns are wiped and replaced. + * + * @param map Cooldowns previously saved. + */ + public void setCooldowns(Map> map) { + cooldowns.clear(); + cooldowns.putAll(map); + } + + /** + * Represents a cooldown timer for a specific recipe. + *

    + * A cooldown consists of: + *

      + *
    • The recipe ID
    • + *
    • The cooldown duration (seconds)
    • + *
    • The timestamp when the cooldown started
    • + *
    + */ + public static class Cooldown { + + private final String recipeID; + private final long duration; // seconds + private final long startTime; // millis + + /** + * Creates a new cooldown timer starting immediately. + * + * @param recipeID The recipe to restrict. + * @param durationSeconds Duration in seconds. + */ + public Cooldown(String recipeID, long durationSeconds) { + this.recipeID = recipeID; + this.duration = durationSeconds; + this.startTime = System.currentTimeMillis(); + } + + /** @return The recipe ID this cooldown belongs to. */ + public String getRecipeID() { + return recipeID; + } + + /** @return The cooldown duration in seconds. */ + public long getDuration() { + return duration; + } + + + /** @return The timestamp when the cooldown began (ms). */ + public long getStartTime() { + return startTime; + } + + /** + * Calculates how many seconds remain. + * + * @return Seconds left, never negative. + */ + public long getTimeLeft() { + long elapsed = (System.currentTimeMillis() - startTime) / 1000; + long left = duration - elapsed; + return Math.max(left, 0); + } + + /** + * Checks whether the cooldown has completed. + * + * @return {@code true} if expired, else false. + */ + public boolean isExpired() { + return getTimeLeft() <= 0; + } + } +} \ No newline at end of file diff --git a/src/me/mehboss/utils/InvalidRecipeException.java b/src/me/mehboss/utils/InvalidRecipeException.java index 1539bf3..f7129fb 100644 --- a/src/me/mehboss/utils/InvalidRecipeException.java +++ b/src/me/mehboss/utils/InvalidRecipeException.java @@ -1,11 +1,27 @@ package me.mehboss.utils; +/** + * Thrown when a recipe fails validation or contains invalid data. + *

    + * This exception signals that a recipe cannot be processed due to issues such as: + *

      + *
    • Missing required fields
    • + *
    • Invalid item definitions
    • + *
    • Malformed ingredient lists
    • + *
    • Unsupported recipe configuration
    • + *
    + * + *

    It extends {@link RuntimeException}, allowing it to be thrown without + * requiring explicit handling or declaration. + */ public class InvalidRecipeException extends RuntimeException { - /** - * - */ private static final long serialVersionUID = 1L; + /** + * Constructs a new InvalidRecipeException with a descriptive error message. + * + * @param message A message explaining why the recipe is invalid. + */ public InvalidRecipeException(String message) { super(message); } diff --git a/src/me/mehboss/utils/InventoryUtils.java b/src/me/mehboss/utils/InventoryUtils.java index 7a4a103..4dd5975 100644 --- a/src/me/mehboss/utils/InventoryUtils.java +++ b/src/me/mehboss/utils/InventoryUtils.java @@ -11,12 +11,45 @@ import com.google.common.collect.Streams; +/** + * Utility class for handling various inventory-related operations such as: + *

      + *
    • Custom click interaction behavior
    • + *
    • Stack merging logic
    • + *
    • Space calculations for inventory storage
    • + *
    • Detecting empty inventory slots
    • + *
    + * + *

    This class is stateless and all methods are static.

    + */ public class InventoryUtils { + /** + * Wrapper method that reads the cursor and clicked item directly from the event. + * + * @param event The inventory click event. + */ public static void calculateClickedSlot(InventoryClickEvent event) { calculateClickedSlot(event, event.getCursor(), event.getCurrentItem()); } + /** + * Manually computes the behavior of clicking inside an inventory, overriding + * Bukkit's default logic. Supports custom stack behavior for both left and + * right clicks. + * + *

    Actions supported:

    + *
      + *
    • Left-click stack swapping
    • + *
    • Left-click stack merge
    • + *
    • Right-click single-item placement
    • + *
    • Right-click stack merge
    • + *
    + * + * @param event The click event being processed. + * @param cursor The item on the player's cursor. + * @param currentItem The item currently in the clicked slot. + */ public static void calculateClickedSlot(final InventoryClickEvent event, final ItemStack cursor, final ItemStack currentItem) { if (cursor == null) @@ -65,34 +98,91 @@ public static void calculateClickedSlot(final InventoryClickEvent event, final I } } + /** + * Checks if an item is null or AIR. + * + * @param item The item to check. + * @return True if null or AIR. + */ public static boolean isAirOrNull(ItemStack item) { return item == null || item.getType().equals(Material.AIR); } + /** + * Checks if an inventory has room for a given number of a specific item. + * + * @param inventory Inventory to check. + * @param itemStack The item type being added. + * @param amount Number of stacks to fit. + * @return True if enough space exists. + */ public static boolean hasInventorySpace(Inventory inventory, ItemStack itemStack, int amount) { return getInventorySpace(inventory, itemStack) >= itemStack.getAmount() * amount; } + /** + * Checks if an inventory has space for one stack of the given item. + * + * @param inventory Inventory to check. + * @param itemStack Item being added. + * @return True if enough space exists. + */ public static boolean hasInventorySpace(Inventory inventory, ItemStack itemStack) { return hasInventorySpace(inventory.getStorageContents(), itemStack); } + /** + * Checks if inventory contents have enough space for the given item. + * + * @param contents Inventory contents. + * @param itemStack Item being added. + * @return True if space is available. + */ public static boolean hasInventorySpace(ItemStack[] contents, ItemStack itemStack) { return getInventorySpace(contents, itemStack) >= itemStack.getAmount(); } + /** + * Checks if a player has room for the given item. + * + * @param p Player whose inventory will be checked. + * @param item Item the player wants to add. + * @return True if enough space exists. + */ public static boolean hasInventorySpace(Player p, ItemStack item) { return getInventorySpace(p, item) >= item.getAmount(); } + /** + * Computes available space for a specific item in a player's inventory. + * + * @param p Player to check. + * @param item Item type that may be added. + * @return The number of items that can fit. + */ public static int getInventorySpace(Player p, ItemStack item) { return getInventorySpace(p.getInventory(), item); } + /** + * Computes available space in an inventory for one specific item type. + * + * @param inventory The inventory to scan. + * @param item The item type being checked. + * @return The total number of items that can fit. + */ public static int getInventorySpace(Inventory inventory, ItemStack item) { return getInventorySpace(inventory.getStorageContents(), item); } + /** + * Computes total available capacity for an item based on empty slots and + * partially filled stacks that match. + * + * @param contents Inventory contents. + * @param item Item type being evaluated. + * @return Total capacity for the item. + */ public static int getInventorySpace(ItemStack[] contents, ItemStack item) { int free = 0; for (ItemStack i : contents) { @@ -105,6 +195,13 @@ public static int getInventorySpace(ItemStack[] contents, ItemStack item) { return free; } + /** + * Checks whether a player has at least a specified number of empty slots. + * + * @param p The player whose inventory to check. + * @param count Minimum number of empty slots needed. + * @return True if at least {@code count} empty slots exist. + */ public static boolean hasEmptySpaces(Player p, int count) { return Streams.stream(p.getInventory()).filter(Objects::isNull).count() >= count; } diff --git a/src/me/mehboss/utils/ItemBuilder.java b/src/me/mehboss/utils/ItemBuilder.java index 4a4be4a..d56eb29 100644 --- a/src/me/mehboss/utils/ItemBuilder.java +++ b/src/me/mehboss/utils/ItemBuilder.java @@ -102,16 +102,17 @@ private static void loadOne(File ymlFile) { } itemConfig = cfg; + String resultPath = cfg.isConfigurationSection(section + ".Result") ? section + ".Result" : section; - Optional built = Main.getInstance().recipeManager.buildItem(section, itemConfig); + Optional built = Main.getInstance().recipeManager.buildItem(resultPath, itemConfig); if (!built.isPresent()) { Main.getInstance().getLogger().warning("[CustomItemAPI] buildItem failed for section " + section); return; } ItemStack it = built.get(); - it = Main.getInstance().recipeManager.handleDurability(it, section); - int amount = cfg.isInt(section + ".Amount") ? cfg.getInt(section + ".Amount") : 1; + it = Main.getInstance().recipeManager.handleDurability(it, resultPath); + int amount = cfg.isInt(resultPath + ".Amount") ? cfg.getInt(resultPath + ".Amount") : 1; it.setAmount(amount); // Identifier is required for lookup diff --git a/src/me/mehboss/utils/MetaChecks.java b/src/me/mehboss/utils/MetaChecks.java index 81f557b..1af1905 100644 --- a/src/me/mehboss/utils/MetaChecks.java +++ b/src/me/mehboss/utils/MetaChecks.java @@ -8,11 +8,35 @@ import me.mehboss.recipe.Main; import me.mehboss.utils.RecipeUtil.Ingredient; +/** + * Handles all metadata comparison logic used during special recipe matching. + *

    + * Provides utility checks for matching item materials, display names, + * and custom identifiers. This class is used internally by RecipeUtil + * and recipe handlers to determine whether an input item matches a + * defined ingredient. + */ public class MetaChecks { RecipeUtil getRecipeUtil() { return Main.getInstance().recipeUtil; } - + + /** + * Compares an input item against a recipe ingredient definition. + *

    + * Validates: + *

      + *
    • null vs non-null mismatches
    • + *
    • material type
    • + *
    • custom identifier match (if present)
    • + *
    • display name match (if present)
    • + *
    + * + * @param recipeName name of the recipe (used for debug output) + * @param item the actual input item + * @param ingredient expected ingredient definition + * @return true if the item satisfies all required checks + */ public Boolean itemsMatch(String recipeName, ItemStack item, Ingredient ingredient) { // Null check first if ((ingredient.isEmpty() && item != null) || (!ingredient.isEmpty() && item == null)) { @@ -47,24 +71,26 @@ public Boolean itemsMatch(String recipeName, ItemStack item, Ingredient ingredie // Checks displayname requirements only } else { - if (ingredient.hasDisplayName() && (!item.hasItemMeta() || !item.getItemMeta().hasDisplayName())) { + if (ingredient.hasDisplayName() + && (!item.hasItemMeta() || !CompatibilityUtil.hasDisplayname(item.getItemMeta()))) { logDebug("Ingredient has displayname, item in slot does not have one", recipeName); return false; } if (ingredient.hasDisplayName() - && !(item.getItemMeta().getDisplayName().equals(ingredient.getDisplayName()))) { + && !(CompatibilityUtil.getDisplayname(item.getItemMeta()).equals(ingredient.getDisplayName()))) { logDebug("Displaynames do not match", recipeName); - logDebug("Slot displayname - " + item.getItemMeta().getDisplayName(), recipeName); + logDebug("Slot displayname - " + CompatibilityUtil.getDisplayname(item.getItemMeta()), recipeName); logDebug("Ingredient displayname - " + ingredient.getDisplayName(), recipeName); return false; } } return true; } - + private void logDebug(String st, String recipeName) { if (Main.getInstance().debug) - Logger.getLogger("Minecraft").log(Level.WARNING, "[DEBUG][" + Main.getInstance().getName() + "][Metachecks][" + recipeName + "] " + st); + Logger.getLogger("Minecraft").log(Level.WARNING, + "[DEBUG][" + Main.getInstance().getName() + "][Metachecks][" + recipeName + "] " + st); } } diff --git a/src/me/mehboss/utils/RecipeConditions.java b/src/me/mehboss/utils/RecipeConditions.java index 8bb69a8..3f22708 100644 --- a/src/me/mehboss/utils/RecipeConditions.java +++ b/src/me/mehboss/utils/RecipeConditions.java @@ -19,14 +19,34 @@ import me.mehboss.recipe.Main; +/** + * Represents all optional conditions that may be required for a recipe + * to be considered valid before crafting/smelting/brewing/etc. + *

    + * This container focuses on external world/state conditions such as + * time-of-day, biome, weather, world allow/deny lists, and more. + *

    + * Each category of conditions is grouped into sub-objects. If a category + * contains no values, that category is ignored. + */ public final class RecipeConditions { + /** + * Conditions related to the world or block environment in which + * the recipe is executed. + */ public static final class Block { // World gating public Set allowWorlds = Collections.emptySet(); // lowercase public Set denyWorlds = Collections.emptySet(); // lowercase - // Time gating: one or more ranges like "13000-23000" + /** + * Represents a range of valid in-game times for recipe conditions. + *

    + * A range may be normal (e.g., {@code 0–12000}) or wrap around the + * midnight boundary (e.g., {@code 23000–2000}). The {@link #contains(int)} + * method automatically supports both. + */ public static final class TimeRange { public final int min; // inclusive public final int max; // inclusive @@ -234,6 +254,28 @@ private static String joinTimeRanges(List ranges) { } } + /** + * Calculates the current moon phase for the given world. + *

    + * Minecraft cycles the moon every 8 in-game days. + * This method determines the phase by taking the world's total time + * and converting it into a day index, then modulo 8. + *

    + * Moon phases (0–7): + *

      + *
    • 0 – Full Moon
    • + *
    • 1 – Waning Gibbous
    • + *
    • 2 – Last Quarter
    • + *
    • 3 – Waning Crescent
    • + *
    • 4 – New Moon
    • + *
    • 5 – Waxing Crescent
    • + *
    • 6 – First Quarter
    • + *
    • 7 – Waxing Gibbous
    • + *
    + * + * @param world the world to read time information from + * @return an integer 0–7 representing the current moon phase + */ public static int getMoonPhase(World world) { long day = world.getFullTime() / 24000L; // Get the in-game day return (int) (day % 8); // Calculate the moon phase (0-7) diff --git a/src/me/mehboss/utils/RecipeUtil.java b/src/me/mehboss/utils/RecipeUtil.java index f19bc1e..ee75fdc 100644 --- a/src/me/mehboss/utils/RecipeUtil.java +++ b/src/me/mehboss/utils/RecipeUtil.java @@ -1,11 +1,14 @@ package me.mehboss.utils; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -15,6 +18,7 @@ import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.recipe.CookingBookCategory; import org.bukkit.inventory.recipe.CraftingBookCategory; import org.bukkit.material.MaterialData; @@ -29,14 +33,70 @@ import io.lumine.mythic.bukkit.MythicBukkit; import io.th0rgal.oraxen.api.OraxenItems; import me.mehboss.recipe.Main; -import me.mehboss.utils.RecipeConditions.ConditionSet; +import me.mehboss.utils.RecipeUtil.Recipe.RecipeType; +import me.mehboss.utils.data.CookingRecipeData; import net.Indyuce.mmoitems.MMOItems; - +import valorless.havenbags.api.HavenBagsAPI; +import valorless.havenbags.datamodels.Data; + +/** + * Utility manager and API entry point for creating, validating, and storing + * custom recipe definitions inside the CustomRecipes plugin. + * + *

    + * This class maintains several internal collections used by the plugin: + *

    + * + *
      + *
    • recipes: A map of recipe names to their corresponding + * {@link Recipe} objects.
    • + *
    • keyList: A list of all registered {@link org.bukkit.NamespacedKey} + * identifiers.
    • + *
    • custom_items: A lookup table for custom ItemStacks used by + * recipes.
    • + *
    • SUPPORTED_PLUGINS: A list of optional hookable item systems + * (ItemsAdder, MythicMobs, ExecutableItems, Oraxen, Nexo, MMOItems).
    • + *
    + * + *

    Recipe Registration

    + *

    + * The {@link #createRecipe(Recipe)} method is the primary entry point for + * adding recipes to CustomRecipes. Before a recipe is accepted, it is validated + * against several rules — if any fail, an {@link InvalidRecipeException} is + * thrown with a descriptive error message. + *

    + * + *

    Validation rules include:

    + *
      + *
    • Null or missing key: The recipe must not be null and must have a + * valid NamespacedKey.
    • + *
    • Ingredient count: Shaped / shapeless recipes must contain exactly + * 9 ingredient slots.
    • + *
    • Result validity: The output item must exist and cannot be + * AIR.
    • + *
    • Shape integrity: Shaped recipes must have non-null rows + * (1–3).
    • + *
    • Single-input rules: Furnace / stonecutter recipes may only have 1 + * ingredient.
    • + *
    + * + *

    + * When validation passes, the recipe is stored internally and becomes + * retrievable by name or its associated key. + *

    + * + *

    + * This class is intended for use by addon developers and internal systems that + * need programmatic control over recipe creation and management. + *

    + */ public class RecipeUtil { + private HashMap recipes = new HashMap<>(); private ArrayList keyList = new ArrayList<>(); - public List SUPPORTED_PLUGINS = List.of("itemsadder", "mythicmobs", "executableitems", "oraxen", "nexo", - "mmoitems"); + private HashMap custom_items = new HashMap<>(); + public List SUPPORTED_PLUGINS = Arrays.asList("itemsadder", "mythicmobs", "executableitems", "oraxen", + "nexo", "mmoitems", "havenbags"); /** * Adds a finished Recipe object to the API @@ -58,8 +118,8 @@ public void createRecipe(Recipe recipe) { throw new InvalidRecipeException(errorMessage); } - if ((recipe.getType() == RecipeUtil.Recipe.RecipeType.SHAPED - || recipe.getType() == RecipeUtil.Recipe.RecipeType.SHAPELESS) && recipe.getIngredientSize() != 9) { + if ((recipe.getType() == RecipeType.SHAPED || recipe.getType() == RecipeType.SHAPELESS) + && recipe.getIngredientSize() != 9) { String errorMessage = "[CRAPI] Could not add recipe: " + recipe.getName() + ". Recipe is " + recipe.getType() + " and does not have 9 ingredients! Ingredients: " + recipe.getIngredientSize(); throw new InvalidRecipeException(errorMessage); @@ -71,8 +131,7 @@ public void createRecipe(Recipe recipe) { throw new InvalidRecipeException(errorMessage); } - if ((recipe.getType() == RecipeUtil.Recipe.RecipeType.SHAPED - || recipe.getType() == RecipeUtil.Recipe.RecipeType.SHAPELESS) + if ((recipe.getType() == RecipeType.SHAPED || recipe.getType() == RecipeType.SHAPELESS) && (recipe.getRow(1) == null || recipe.getRow(2) == null || recipe.getRow(3) == null)) { String errorMessage = "[CRAPI] Could not add recipe because shape cannot have null rows. Recipe: " + recipe.getName(); @@ -80,7 +139,8 @@ public void createRecipe(Recipe recipe) { } recipes.put(recipe.getName(), recipe); - keyList.add(recipe.getKey()); + if (!keyList.contains(recipe.getKey())) + keyList.add(recipe.getKey()); } /** @@ -90,11 +150,45 @@ public void createRecipe(Recipe recipe) { */ public void removeRecipe(String recipeName) { if (recipes.containsKey(recipeName)) { - clearDuplicates(recipes.get(recipeName)); + Recipe recipe = recipes.get(recipeName); + clearDuplicates(recipe); + keyList.remove(recipe.getKey()); recipes.remove(recipeName); } } + /** + * Loads the specified recipe to the server. Checks and corrects duplicate + * NamespacedKey. + * + * @param recipe the recipe you want to load + */ + public void registerRecipe(Recipe recipe) { + this.clearDuplicates(recipe); + Main.getInstance().recipeManager.addRecipesFromAPI(recipe); + } + + /** + * Resets and reloads all registered recipes, including CR recipes. Checks and + * corrects duplicate NamespacedKey. + */ + public void reloadRecipes() { + this.clearDuplicates(null); + Main.getInstance().recipeManager.addRecipesFromAPI(null); + } + + /** + * Extracts the plugin namespace from a custom item key. + *

    + * Custom item keys typically follow the format: + * + *

    +	 * pluginNamespace:itemId
    +	 * 
    + * + * @param key the full custom item key (e.g. "mmoitems:SWORD_1") + * @return the namespace before the colon, or null if the key is invalid. + */ public String getCustomItemPlugin(String key) { if (key == null) return null; @@ -107,6 +201,15 @@ public String getCustomItemPlugin(String key) { return namespace; } + /** + * Checks whether a namespace belongs to a supported custom-item plugin. + *

    + * This only validates the namespace string (e.g. "mmoitems") — it does not + * check if the plugin is installed or if the item exists. + * + * @param key the plugin namespace to check + * @return true if the namespace matches a known custom-item plugin. + */ public Boolean isCustomItem(String key) { if (key == null) return false; @@ -150,7 +253,7 @@ public ItemStack getResultFromKey(String key) { String recipe = split.length > 2 ? split[2] : ""; String item = namespace + ":" + itemId; - if (Main.getInstance().serverVersionLessThan(1, 15)) { + if (Main.getInstance().serverVersionLessThan(1, 15) && (split.length < 2)) { logError("Issue detected with recipe.. ", recipe); logError("Your server version does not support custom items from other plugins!", recipe); logError("You must be on 1.15 or higher!", recipe); @@ -178,8 +281,15 @@ public ItemStack getResultFromKey(String key) { if (Main.getInstance().hasCustomPlugin("executableitems")) { Optional ei = ExecutableItemsAPI.getExecutableItemsManager() .getExecutableItem(itemId); - if (ei.isPresent()) - return ei.get().buildItem(1, Optional.empty()); + if (ei.isPresent()) { + if (custom_items.containsKey(itemId)) { + return custom_items.get(itemId); + } else { + ItemStack eiItem = ei.get().buildItem(1, Optional.empty()); + custom_items.put(itemId, eiItem); + return eiItem; + } + } } break; @@ -212,6 +322,28 @@ public ItemStack getResultFromKey(String key) { return mmoitem; } break; + + case "havenbags": + boolean hasArgs = split.length >= 3; + Material bagMaterial = hasArgs && XMaterial.matchXMaterial(split[2]).isPresent() + ? XMaterial.matchXMaterial(split[2]).get().get() + : null; + int size = !hasArgs || Integer.getInteger(split[1]) == null ? 1 : Integer.parseInt(split[1]); + int bagCMD = split.length < 4 || Integer.getInteger(split[3]) == null ? 0 : Integer.parseInt(split[3]); + String canBind = split.length >= 5 ? split[4] : "null"; + String bagTexture = split.length >= 6 ? split[5] : "none"; + + if (!hasArgs || bagMaterial == null) { + logError("Could not complete recipe because HavenBags is missing either a material or bag size. Key: " + + item, recipe); + return null; + } + + // havenbags:size:material:customModelData:canBind:texture + ItemStack bagItem = Main.getInstance().recipeManager.handleBagCreation(bagMaterial, size, bagCMD, canBind, + bagTexture, null); + return bagItem; + } if (SUPPORTED_PLUGINS.contains(namespace) && split.length > 2) { @@ -226,33 +358,6 @@ public ItemStack getResultFromKey(String key) { return null; } - /** - * Getter for a recipe from the result ItemStack - * - * @param item the ItemStack - * @return the Recipe that is found, can be null - */ - public Recipe getRecipeFromResult(ItemStack item) { - - if (item == null) - return null; - - String id = NBTEditor.contains(item, NBTEditor.CUSTOM_DATA, "CUSTOM_ITEM_IDENTIFIER") - ? NBTEditor.getString(item, NBTEditor.CUSTOM_DATA, "CUSTOM_ITEM_IDENTIFIER") - : "none"; - - if (!id.equals("none")) - return getRecipeFromKey(id); - - for (Recipe recipe : recipes.values()) { - ItemStack result = recipe.getResult(); - - if (result.equals(item) || result.isSimilar(item)) - return recipe; - } - return null; - } - /** * Getter for a key from the result ItemStack * @@ -260,7 +365,7 @@ public Recipe getRecipeFromResult(ItemStack item) { * @return the key that is found, can be null */ public String getKeyFromResult(ItemStack item) { - if (item == null) + if (item == null || !item.hasItemMeta()) return null; // First, try to get a key from normal recipes @@ -270,7 +375,7 @@ public String getKeyFromResult(ItemStack item) { // Then, try for a custom item String customID = ItemBuilder.get(item); - if (customID != null && !recipe.isCustomItem()) + if (customID != null) return customID; if (Main.getInstance().hasCustomPlugin("itemsadder")) { @@ -314,6 +419,18 @@ public String getKeyFromResult(ItemStack item) { } } + if (Main.getInstance().hasCustomPlugin("havenbags")) { + Data bagData = HavenBagsAPI.getBagData(item); + if (bagData != null) { + Material material = bagData.getMaterial(); + String texture = bagData.getTexture(); + int modelData = bagData.getModeldata(); + int size = bagData.getSize(); + + // havenbags:size:material:customModelData:texture + return "havenbags:" + size + material + modelData + texture; + } + } return null; } @@ -346,13 +463,74 @@ public Recipe getRecipe(String recipeName) { return recipes.containsKey(recipeName) ? recipes.get(recipeName) : null; } + /** + * Getter for a recipe from the result ItemStack + * + * @param item the ItemStack + * @return the Recipe that is found, can be null + */ + public Recipe getRecipeFromResult(ItemStack item) { + + if (item == null || item.getType() == Material.AIR) + return null; + + String id = NBTEditor.contains(item, NBTEditor.CUSTOM_DATA, "CUSTOM_ITEM_IDENTIFIER") + ? NBTEditor.getString(item, NBTEditor.CUSTOM_DATA, "CUSTOM_ITEM_IDENTIFIER") + : "none"; + + if (!id.equals("none")) + return getRecipeFromKey(id); + + for (Recipe recipe : recipes.values()) { + ItemStack result = recipe.getResult(); + + if (result.equals(item) || result.isSimilar(item)) + return recipe; + + // hacky fix for 1.8.8 issue of ItemMeta lazy population + if (Main.getInstance().serverVersionLessThan(1, 12)) { + ItemMeta recipeMeta = result.getItemMeta(); + ItemMeta itemMeta = item.getItemMeta(); + + if (recipeMeta == null || itemMeta == null) + continue; + + boolean typeMatches = recipe.getResult().getType() == item.getType(); + String iName = itemMeta.hasDisplayName() ? itemMeta.getDisplayName() : "none"; + String rName = recipeMeta.hasDisplayName() ? recipeMeta.getDisplayName() : "none"; + List rLore = recipeMeta.hasLore() ? normalizeList(recipeMeta.getLore()) : Collections.emptyList(); + List iLore = itemMeta.hasLore() ? normalizeList(itemMeta.getLore()) : Collections.emptyList(); + + if (rName.equals(iName) && rLore.equals(iLore) && typeMatches) + return recipe; + } + } + return null; + } + + private List normalizeList(List lore) { + return lore.stream() + .map(line -> ChatColor.translateAlternateColorCodes('&', line)) + .map(String::trim) + .collect(Collectors.toList()); + } + + /** + * Getter for all recipes keys/identifiers + * + * @return an ArrayList of Keys, can be Empty. + */ + public ArrayList getAllKeys() { + return keyList.isEmpty() ? new ArrayList() : keyList; + } + /** * Getter for all recipes registered * * @return a hashmap of recipes, including CR recipes, can be null */ public HashMap getAllRecipes() { - return recipes.isEmpty() ? null : recipes; + return recipes.isEmpty() ? new HashMap() : recipes; } /** @@ -392,7 +570,7 @@ private boolean isSimilarSafe(ItemStack a, ItemStack b) { * @param type the recipe type * @return the hashmap of recipes found, can be null */ - public HashMap getRecipesFromType(Recipe.RecipeType type) { + public HashMap getRecipesFromType(RecipeType type) { if (recipes.isEmpty()) return null; @@ -424,19 +602,38 @@ public ArrayList getAllResults() { return results; } + /** + * Getter for all recipe names + * + * @return a set of strings + */ + public ArrayList getRecipeNames() { + return recipes.isEmpty() ? new ArrayList<>() : new ArrayList<>(recipes.keySet()); + } + + /* + * ====================================================================== + * Cooking-Related Accessors (Furnace / etc.) + * ====================================================================== + */ + /** * Getter for a furnace recipe by the source * * @return recipe the recipe that matches */ public Recipe getRecipeFromFurnaceSource(ItemStack item) { - HashMap furnaceRecipes = getRecipesFromType(Recipe.RecipeType.FURNACE); + HashMap furnaceRecipes = getRecipesFromType(RecipeType.FURNACE); if (furnaceRecipes == null) return null; for (Recipe recipes : furnaceRecipes.values()) { - ItemStack sourceItem = recipes.getFurnaceSource(); + if (!(recipes instanceof CookingRecipeData)) + continue; + + CookingRecipeData cookRecipe = (CookingRecipeData) recipes; + ItemStack sourceItem = cookRecipe.getSource(); if (sourceItem != null && (item.equals(sourceItem) || item.isSimilar(sourceItem))) return recipes; @@ -444,34 +641,11 @@ public Recipe getRecipeFromFurnaceSource(ItemStack item) { return null; } - /** - * Getter for all recipe names - * - * @return a set of strings - */ - public ArrayList getRecipeNames() { - return recipes.isEmpty() ? new ArrayList<>() : new ArrayList<>(recipes.keySet()); - } - - /** - * Loads the specified recipe to the server. Checks and corrects duplicate - * NamespacedKey. - * - * @param recipe the recipe you want to load - */ - public void addRecipe(Recipe recipe) { - this.clearDuplicates(recipe); - Main.getInstance().recipeManager.addRecipesFromAPI(recipe); - } - - /** - * Resets and reloads all registered recipes, including CR recipes. Checks and - * corrects duplicate NamespacedKey. + /* + * ====================================================================== + * Internal Helpers (private) + * ====================================================================== */ - public void reloadRecipes() { - this.clearDuplicates(null); - Main.getInstance().recipeManager.addRecipesFromAPI(null); - } /** * Clears all or specific registered recipe(s) for duplicate NamespacedKey, @@ -516,21 +690,62 @@ private void clearDuplicates(Recipe recipe) { } } + private void logError(String st, String recipe) { + Logger.getLogger("Minecraft").log(Level.WARNING, + "[DEBUG][" + Main.getInstance().getName() + "][" + recipe + "] " + st); + } + + /* + * ====================================================================== + * Classes (Recipe, Ingredient) + * ====================================================================== + */ + + /** + * Represents a fully defined custom recipe used by the CustomRecipes API. + *

    + * A Recipe stores its output item, ingredient layout, behavior flags, + * permissions, cooldowns, optional commands, and metadata such as whether the + * recipe is shaped, shapeless, furnace-based, etc. + *

    + * + *

    + * Only validated recipes should be added through + * {@link RecipeUtil#createRecipe}. + *

    + * + *

    Key features:

    + *
      + *
    • result – the final ItemStack produced by crafting.
    • + *
    • ingredients – a list of ingredient entries, optionally exact.
    • + *
    • commands – commands executed when the recipe is crafted.
    • + *
    • name/key – human-readable name and NamespacedKey identifier.
    • + *
    • disabledWorlds – worlds where this recipe cannot be used.
    • + *
    • flags – behavior options such as exactChoice, ignoreData, + * ignoreModelData, ignoreNames, discoverable, grantItem, etc.
    • + *
    • shape rows – row1, row2, row3 for shaped crafting layouts.
    • + *
    • cooldown – optional per-player cooldown before recrafting.
    • + *
    + * + *

    + * This class serves as a simple data container. Logic is handled in RecipeUtil + * during registration and validation. + *

    + */ public static class Recipe { private ItemStack result; + private List commands; private ArrayList disabledWorlds = new ArrayList<>(); - private ArrayList leftoverItems = new ArrayList<>(); private ArrayList ingredients; private String customItem; private String name; private String key; private String permission; - private List commands; - private boolean exactChoice = false; + private boolean exactChoice = true; private boolean placeable = true; private boolean active = true; private boolean ignoreData = false; @@ -538,26 +753,37 @@ public static class Recipe { private boolean ignoreNames = false; private boolean isTagged = false; private boolean discoverable = false; + private boolean hasCommands = false; private boolean isGrantItem = true; - - private ItemStack furnaceSource; - private String row1; private String row2; private String row3; private long cooldown = 0; - private int cookTime = 200; - private int anvilCost = 0; - private float furnaceExperience = 1.0f; - private String group = ""; + public enum RecipeType { + SHAPELESS(), SHAPED(), STONECUTTER(), FURNACE(), ANVIL(), BLASTFURNACE("BLAST", "BLAST_FURNACE"), SMOKER(), + CAMPFIRE(), GRINDSTONE(), BREWING_STAND("BREWING"); - private ConditionSet conditionSet = new ConditionSet(); + private final Set aliases; - public enum RecipeType { - SHAPELESS, SHAPED, STONECUTTER, FURNACE, ANVIL, BLASTFURNACE, SMOKER, CAMPFIRE, GRINDSTONE, BREWING_STAND; + RecipeType(String... aliases) { + this.aliases = new HashSet<>(); + for (String alias : aliases) + this.aliases.add(alias.toUpperCase().replace(" ", "_")); + this.aliases.add(this.name()); + } + + public static RecipeType fromString(String name) { + if (name == null) + return SHAPED; + String key = name.toUpperCase().replace(" ", "_"); + for (RecipeType type : values()) + if (type.aliases.contains(key)) + return type; + return SHAPED; + } } private RecipeType recipeType = RecipeType.SHAPED; @@ -574,26 +800,6 @@ public Recipe(String name) { this.ingredients = new ArrayList<>(); } - /** - * Gets the ConditionSet attached to this recipe. Conditions define extra - * requirements for crafting (e.g. world, time, weather). - * - * @return the ConditionSet for this recipe, never null - */ - public ConditionSet getConditionSet() { - return conditionSet; - } - - /** - * Sets the ConditionSet for this recipe. If {@code cs} is null, an empty - * ConditionSet is applied instead. - * - * @param cs the new ConditionSet for this recipe, can be null - */ - public void setConditionSet(ConditionSet cs) { - this.conditionSet = (cs == null) ? new ConditionSet() : cs; - } - /** * Getter for setBookCategory * @@ -686,52 +892,6 @@ public void setExactChoice(Boolean exactChoice) { this.exactChoice = exactChoice; } - /** - * Sets whether the recipe output should be granted after a command is performed - * - * @return true if the recipe is granted, false otherwise. - */ - public void setGrantItem(Boolean grantItem) { - this.isGrantItem = grantItem; - } - - /** - * Gets whether the recipe output should be granted after a command is performed - * - * @return true if the recipe is granted, false otherwise. - */ - public boolean isGrantItem() { - return isGrantItem; - } - - /** - * Gets whether the recipe is an item or a command - * - * @return true if the recipe is a command, false otherwise - */ - public boolean hasCommands() { - return hasCommands; - } - - /** - * Sets the commands to perform upon crafting a recipe - * - * @param commands the list of command strings - */ - public void setCommands(List commands) { - this.commands = commands; - this.hasCommands = true; - } - - /** - * Gets the commands to perform upon crafting a recipe - * - * @return the list of commands to be performed - */ - public List getCommand() { - return commands; - } - /** * Getter for setPlaceable * @@ -1014,6 +1174,25 @@ public void addIngredient(Ingredient ingredient) { this.ingredients.add(ingredient); } + /** + * Compiles all ingredient materials of a recipe + * + * @returns an ArrayList of all ingredient types + */ + public ArrayList getAllIngredientTypes() { + ArrayList types = new ArrayList<>(); + if (this.ingredients.isEmpty()) + return types; + + for (Ingredient ingredient : this.ingredients) { + if (ingredient.isEmpty()) + continue; + types.add(ingredient.getMaterial()); + + } + return types; + } + /** * Getter for the recipe ingredients * @@ -1032,89 +1211,6 @@ public int getIngredientSize() { return ingredients.size(); } - /** - * Setter for the cook time of a furnace recipe - * - * @param cooktime how long it takes to cook the ingredient, in seconds - */ - public void setCookTime(int cooktime) { - this.cookTime = cooktime; - } - - /** - * Getter for the cook time of a furnace recipe - * - * @returns how long it takes to cook the ingredient - */ - public int getCookTime() { - return cookTime; - } - - /** - * Setter for the experience given from a furnace recipe - * - * @param experience the amount of experience given - */ - public void setExperience(float experience) { - this.furnaceExperience = experience; - } - - /** - * Getter for the furnace experience of a furnace recipe result - * - * @returns the amount of experience granted from a furnace result - */ - public float getExperience() { - return furnaceExperience; - } - - /** - * Setter for the group of a stonecutter recipe - * - * @param group the group of a stonecutter recipe - */ - public void setGroup(String group) { - this.group = group; - } - - /** - * Getter for a stonecutter group - * - * @returns the group of a stonecutter recipe - */ - public String getGroup() { - return group; - } - - /** - * Setter for the cost required for an anvil recipe - * - * @param cost the cost required to complete an anvil transaction - */ - public void setRepairCost(int cost) { - this.anvilCost = cost; - } - - /** - * Getter for the anvil cost of an anvil usage - * - * @returns the amount of cost required from an anvil result - */ - public int getRepairCost() { - return anvilCost; - } - - /** - * Getter for if the anvil recipe has a repair cost - * - * @returns true or false - */ - public boolean hasRepairCost() { - if (anvilCost == 0) - return false; - return true; - } - /** * Getter for an ingredient slot * @@ -1132,6 +1228,9 @@ public Ingredient getSlot(int slot) throws ArrayIndexOutOfBoundsException { * @param permission the permission required */ public void setPerm(String permission) { + if (permission.equalsIgnoreCase("none")) + permission = null; + this.permission = permission; } @@ -1167,55 +1266,92 @@ public ArrayList getDisabledWorlds() { } /** - * Setter for adding an ingredient as a leftover + * Sets whether the recipe output should be granted after a command is performed * - * @param id the id of the ingredient to be leftover. + * @return true if the recipe is granted, false otherwise. */ - public void addLeftoverItem(String id) { - leftoverItems.add(id); + public void setGrantItem(Boolean grantItem) { + this.isGrantItem = grantItem; } /** - * Getter for ingredients that are leftover for the recipe + * Gets whether the recipe output should be granted after a command is performed * - * @returns an arraylist of ids + * @return true if the recipe is granted, false otherwise. */ - public Boolean isLeftover(String id) { - if (leftoverItems.isEmpty()) - return false; + public boolean isGrantItem() { + return isGrantItem; + } - if (XMaterial.matchXMaterial(id).isPresent()) - id = XMaterial.matchXMaterial(id).get().parseMaterial().toString(); + /** + * Gets whether the recipe is an item or a command + * + * @return true if the recipe is a command, false otherwise + */ + public boolean hasCommands() { + return hasCommands; + } - if (leftoverItems.contains(id)) - return true; + /** + * Sets the commands to perform upon crafting a recipe + * + * @param commands the list of command strings + */ + public void setCommands(List commands) { + this.commands = commands; + this.hasCommands = true; + } - return false; + /** + * Gets the commands to perform upon crafting a recipe + * + * @return the list of commands to be performed + */ + public List getCommand() { + return commands; } /** - * Getter for the source of a furnace recipe - * - * @return a itemstack which represents the furnaces source + * Sets the effects that a recipe gives + * + * @param effects a list of potion effects */ - public ItemStack getFurnaceSource() { - return furnaceSource; + public void setEffects(List effects) { } /** - * Setter for the source of a furnace recipe - * - * @param item the furnace source itemstack + * Gets the effects that a recipe gives + * + * @return a string list of potion effects */ - public void setFurnaceSource(ItemStack item) { - furnaceSource = item; + public List getEffects() { + return null; + } } + /** + * Represents a single ingredient used in a recipe. An Ingredient may define a + * specific ItemStack, a base Material, a list of acceptable material choices, + * custom display/name matching options, and metadata such as slot position, + * amount, and model data. + * + *

    + * Ingredients are created with an abbreviation (the shaped-crafting key + * character) and may be configured with additional properties depending on the + * recipe type. + *

    + * + *

    + * This class acts as a data container. Matching logic is performed elsewhere + * when validating recipe input. + *

    + */ public static class Ingredient { private ItemStack item; private Material material; private MaterialData materialData; + private List materialChoices = new ArrayList<>(); private String displayName = "false"; private String abbreviation; @@ -1378,6 +1514,17 @@ public Material getMaterial() { return material == null ? Material.AIR : material; } + /** + * Setter for the material of the ingredient + * + * This should NEVER be used, except internally! + * + * @param material the material to be used + */ + public void setMaterial(Material material) { + this.material = material; + } + /** * Gets the legacy {@link MaterialData} of this ingredient. * @@ -1414,6 +1561,29 @@ public void setMaterialData(MaterialData data) { materialData = data; } + /** + * Adds an additional valid {@link Material} for this ingredient. + * + *

    + * When material choices are defined, the ingredient will match if the input + * item's material is any of those added here. + *

    + * + * @param material a valid material option for this ingredient + */ + public void addMaterialChoice(Material material) { + materialChoices.add(material); + } + + /** + * Gets all additional materials that may satisfy this ingredient. + * + * @return a list of valid material choices + */ + public List getMaterialChoices() { + return materialChoices.isEmpty() ? Collections.singletonList(material) : materialChoices; + } + /** * Checks whether this ingredient has legacy {@link MaterialData}. * @@ -1435,6 +1605,15 @@ public boolean hasMaterialData() { return true; } + /** + * Checks whether this ingredient has multiple valid materials. + * + * @return true if one or more material choices are defined + */ + public boolean hasMaterialChoices() { + return materialChoices.size() > 1; + } + /** * Getter for the abbreviation of the ingredient, like G for grass Used when * shaping the recipe @@ -1475,9 +1654,4 @@ public int getCustomModelData() { return modelData; } } - - private void logError(String st, String recipe) { - Logger.getLogger("Minecraft").log(Level.WARNING, - "[DEBUG][" + Main.getInstance().getName() + "][" + recipe + "] " + st); - } } \ No newline at end of file diff --git a/src/me/mehboss/utils/data/BrewingRecipeData.java b/src/me/mehboss/utils/data/BrewingRecipeData.java new file mode 100644 index 0000000..562eb19 --- /dev/null +++ b/src/me/mehboss/utils/data/BrewingRecipeData.java @@ -0,0 +1,258 @@ +package me.mehboss.utils.data; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import me.mehboss.brewing.BrewAction; +import me.mehboss.recipe.Main; +import me.mehboss.utils.RecipeUtil.Ingredient; +import me.mehboss.utils.RecipeUtil.Recipe; + +/** + * Stores all extra data required for a custom brewing-stand recipe. + *

    + * Extends {@link Recipe} by adding fields used specifically by brewing logic: + * ingredient item, fuel item, fuel behavior, bottle requirements, brew-action + * behavior, and slot indexing. + *

    + * This is a flexible data container. Recipes may come from config, code, + * integrations, or any other source. + */ +public class BrewingRecipeData extends Recipe { + private ItemStack brewIngredient; + private ItemStack brewFuel; + private BrewAction brewAction; + private boolean brewPerfect = true; + private int brewFuelSet = 40; + private int brewFuelCharge = 10; + private int brewIngredientSlot = 3; + private int brewFuelSlot = 4; + + private boolean requiresBottles = false; + private Material requiredBottleType = Material.POTION; + + public BrewingRecipeData(String name) { + super(name); + } + + /** + * Builds and stores the ingredient item used for brewing. + * Supports custom identifiers or vanilla material/meta. + * + * @param ingredientItem the ingredient definition to build the item from + */ + public void setBrewIngredient(Ingredient ingredientItem) { + ItemStack ingredient = null; + + if (ingredientItem.hasIdentifier()) { + ingredient = Main.getInstance().getRecipeUtil().getResultFromKey(ingredientItem.getIdentifier()); + } + + if (ingredient == null) { + ingredient = new ItemStack(ingredientItem.getMaterial()); + ItemMeta meta = ingredient.getItemMeta(); + + if (ingredientItem.hasDisplayName()) + meta.setDisplayName(ingredientItem.getDisplayName()); + + if (ingredientItem.hasCustomModelData()) + meta.setCustomModelData(ingredientItem.getCustomModelData()); + + ingredient.setItemMeta(meta); + } + + brewIngredient = ingredient; + } + + /** + * Gets the brewing ingredient item. + * + * @return ingredient item created for this recipe + */ + public ItemStack getBrewIngredient() { + return brewIngredient; + } + + /** + * Builds and stores the fuel item used for brewing. + * Supports custom identifiers or vanilla material/meta. + * + * @param fuelItem the fuel definition used to construct the item + */ + public void setBrewFuel(Ingredient fuelItem) { + ItemStack fuel = null; + + if (fuelItem.hasIdentifier()) { + fuel = Main.getInstance().getRecipeUtil().getResultFromKey(fuelItem.getIdentifier()); + } + + if (fuel == null) { + fuel = new ItemStack(fuelItem.getMaterial()); + ItemMeta meta = fuel.getItemMeta(); + + if (fuelItem.hasDisplayName()) + meta.setDisplayName(fuelItem.getDisplayName()); + + if (fuelItem.hasCustomModelData()) + meta.setCustomModelData(fuelItem.getCustomModelData()); + + fuel.setItemMeta(meta); + } + + brewFuel = fuel; + } + + /** + * Gets the brewing fuel item. + * + * @return fuel item created for this recipe + */ + public ItemStack getBrewFuel() { + return brewFuel; + } + + /** + * Sets how this recipe behaves when brewing completes. + * + * @param action the brewing action to perform on completion + */ + public void setBrewAction(BrewAction action) { + brewAction = action; + } + + /** + * Gets the brew action executed when brewing finishes. + * + * @return assigned brew action + */ + public BrewAction getBrewAction() { + return brewAction; + } + + /** + * Enables or disables exact matching for ingredient and fuel items. + * + * @param perfect true to require exact item matching + */ + public void setBrewPerfect(boolean perfect) { + brewPerfect = perfect; + } + + /** + * Checks if ingredient and fuel must match exactly. + * + * @return true if strict matching is enabled + */ + public boolean isBrewPerfect() { + return brewPerfect; + } + + /** + * Sets how much internal fuel is added when consuming a fuel item. + * + * @param value amount of internal fuel to add + */ + public void setBrewFuelSet(int value) { + brewFuelSet = value; + } + + /** + * Gets how much internal fuel is added per fuel item. + * + * @return fuel added per item + */ + public int getBrewFuelSet() { + return brewFuelSet; + } + + /** + * Sets how much internal fuel is consumed per brewing cycle. + * + * @param value amount of fuel consumed + */ + public void setBrewFuelCharge(int value) { + brewFuelCharge = value; + } + + /** + * Gets how much fuel is consumed per brew cycle. + * + * @return fuel consumption amount + */ + public int getBrewFuelCharge() { + return brewFuelCharge; + } + + /** + * Sets which slot is used for the ingredient. + * + * @param slot inventory slot index + */ + public void setBrewIngredientSlot(int slot) { + brewIngredientSlot = slot; + } + + /** + * Gets the slot index used for the ingredient. + * + * @return ingredient slot index + */ + public int getBrewIngredientSlot() { + return brewIngredientSlot; + } + + /** + * Sets which slot is used for the fuel. + * + * @param slot inventory slot index + */ + public void setBrewFuelSlot(int slot) { + brewFuelSlot = slot; + } + + /** + * Gets the slot index used for the fuel. + * + * @return fuel slot index + */ + public int getBrewFuelSlot() { + return brewFuelSlot; + } + + /** + * Enables or disables bottle validation for brewing results. + * + * @param requiresBottles true to require bottle-type slots + */ + public void setRequiresBottles(boolean requiresBottles) { + this.requiresBottles = requiresBottles; + } + + /** + * Checks whether bottle validation is required. + * + * @return true if valid bottle items must be present + */ + public boolean requiresBottles() { + return requiresBottles; + } + + /** + * Sets the type of bottle required for valid brewing output. + * + * @param type the material representing the required bottle + */ + public void setRequiredBottleType(Material type) { + this.requiredBottleType = type; + } + + /** + * Gets the required bottle type for this recipe. + * + * @return bottle material required + */ + public Material getRequiredBottleType() { + return requiredBottleType; + } +} \ No newline at end of file diff --git a/src/me/mehboss/utils/data/CookingRecipeData.java b/src/me/mehboss/utils/data/CookingRecipeData.java new file mode 100644 index 0000000..312362d --- /dev/null +++ b/src/me/mehboss/utils/data/CookingRecipeData.java @@ -0,0 +1,78 @@ +package me.mehboss.utils.data; + +import org.bukkit.inventory.ItemStack; + + +import me.mehboss.utils.RecipeUtil.Recipe; +/** + * Stores extra data for custom furnace-based recipes. + *

    + * Extends {@link Recipe} by adding furnace-specific fields such as + * source item, cook time, and experience yield. This class is a simple + * data container and does not enforce how recipes are created. + */ +public class CookingRecipeData extends Recipe { + + private ItemStack source; + private int cookTime = 200; + private float experience = 1.0f; + + public CookingRecipeData(String name) { + super(name); + } + + /** + * Sets the input item that this recipe smelts or cooks. + * + * @param source the furnace input item + */ + public void setSource(ItemStack source) { + this.source = source; + } + + /** + * Gets the input item used by this cooking recipe. + * + * @return the source item + */ + public ItemStack getSource() { + return source; + } + + /** + * Sets the cook time in ticks. + * (20 ticks = 1 second) + * + * @param cookTime the number of ticks required to cook + */ + public void setCookTime(int cookTime) { + this.cookTime = cookTime; + } + + /** + * Gets how long the recipe takes to cook. + * + * @return cook time in ticks + */ + public int getCookTime() { + return cookTime; + } + + /** + * Sets the experience granted when the recipe is completed. + * + * @param experience the XP amount awarded + */ + public void setExperience(float experience) { + this.experience = experience; + } + + /** + * Gets the experience value awarded when the item is cooked. + * + * @return experience amount + */ + public float getExperience() { + return experience; + } +} \ No newline at end of file diff --git a/src/me/mehboss/utils/data/CraftingRecipeData.java b/src/me/mehboss/utils/data/CraftingRecipeData.java new file mode 100644 index 0000000..c65709a --- /dev/null +++ b/src/me/mehboss/utils/data/CraftingRecipeData.java @@ -0,0 +1,104 @@ +package me.mehboss.utils.data; + +import java.util.ArrayList; + +import com.cryptomorin.xseries.XMaterial; + +import me.mehboss.utils.RecipeConditions.ConditionSet; +import me.mehboss.utils.RecipeUtil.Recipe; + +/** + * Stores additional data for custom crafting recipes. + *

    + * Extends {@link Recipe} with fields used only by crafting systems, + * including leftover item rules, conditional requirements, and + * optional recipe grouping. This class acts only as a data container + * and does not enforce how a recipe is created. + */ +public class CraftingRecipeData extends Recipe { + private ArrayList leftoverItems = new ArrayList<>(); + private ConditionSet conditionSet = new ConditionSet(); + private String group = ""; + + public CraftingRecipeData(String name) { + super(name); + } + + /** + * Setter for adding an ingredient as a leftover + * + * @param id the id of the ingredient to be leftover. + */ + public void addLeftoverItem(String id) { + leftoverItems.add(id); + } + + /** + * Checks if a MATERIAL is marked as leftover. + * + * @returns true or false boolean + */ + public Boolean isLeftover(String id) { + if (leftoverItems.isEmpty()) + return false; + + if (XMaterial.matchXMaterial(id).isPresent()) + id = XMaterial.matchXMaterial(id).get().parseMaterial().toString(); + + if (leftoverItems.contains(id)) + return true; + + return false; + } + + /** + * Getter for the recipe group. + * + *

    + * Available since Spigot 1.13+ + *

    + * + * @return the group string this recipe belongs to. Empty string means no group. + */ + public String getGroup() { + return group; + } + + /** + * Sets the group of the recipe. Recipes with the same group may be grouped + * together when displayed in the client. + * + *

    + * Available since Spigot 1.13+ + *

    + * + * @param group the group name. Empty string denotes no group. + */ + public void setGroup(String group) { + if (group == null) { + this.group = ""; + } else { + this.group = group; + } + } + + /** + * Gets the ConditionSet attached to this recipe. Conditions define extra + * requirements for crafting (e.g. world, time, weather). + * + * @return the ConditionSet for this recipe, never null + */ + public ConditionSet getConditionSet() { + return conditionSet; + } + + /** + * Sets the ConditionSet for this recipe. If {@code cs} is null, an empty + * ConditionSet is applied instead. + * + * @param cs the new ConditionSet for this recipe, can be null + */ + public void setConditionSet(ConditionSet cs) { + this.conditionSet = (cs == null) ? new ConditionSet() : cs; + } +} diff --git a/src/me/mehboss/utils/data/WorkstationRecipeData.java b/src/me/mehboss/utils/data/WorkstationRecipeData.java new file mode 100644 index 0000000..b660d84 --- /dev/null +++ b/src/me/mehboss/utils/data/WorkstationRecipeData.java @@ -0,0 +1,90 @@ +package me.mehboss.utils.data; + +import me.mehboss.utils.RecipeUtil.Recipe; + +/** + * Stores additional data for workstation-based recipes such as + * anvils or stonecutters. + *

    + * Extends {@link Recipe} by adding fields related to workstation + * behavior, including repair cost, experience output, and optional + * grouping. This class is a flexible data container and does not + * define how recipes must be created. + */ +public class WorkstationRecipeData extends Recipe { + private String group = ""; + private int repairCost = 0; + private float experience = 1.0f; + + public WorkstationRecipeData(String name) { + super(name); + } + + /** + * Sets the repair cost for this recipe (used by anvil mechanics). + * + * @param cost the repair cost to assign + */ + public void setRepairCost(int cost) { + this.repairCost = cost; + } + + /** + * Gets the repair cost assigned to this workstation recipe. + * + * @return numerical repair cost + */ + public int getRepairCost() { + return repairCost; + } + + /** + * Checks whether this recipe has a defined repair cost. + * + * @return true if repairCost > 0 + */ + public boolean hasRepairCost() { + return repairCost > 0; + } + + /** + * Sets the amount of experience granted by this recipe. + * + * @param experience the XP reward value + */ + public void setExperience(float experience) { + this.experience = experience; + } + + /** + * Gets the experience reward assigned to this recipe. + * + * @return experience value + */ + public float getExperience() { + return experience; + } + + /** + * Gets the group tag assigned to this recipe. + *

    + * Available since Spigot 1.13+. + * + * @return the recipe group name, or empty string if ungrouped + */ + public String getGroup() { + return group; + } + + /** + * Sets the group this recipe belongs to. + * Recipes with the same group may be visually grouped in the client. + *

    + * Available since Spigot 1.13+. + * + * @param group the group identifier, or empty string for no group + */ + public void setGroup(String group) { + this.group = (group == null ? "" : group); + } +} \ No newline at end of file diff --git a/src/me/mehboss/utils/data/package-info.java b/src/me/mehboss/utils/data/package-info.java new file mode 100644 index 0000000..287612d --- /dev/null +++ b/src/me/mehboss/utils/data/package-info.java @@ -0,0 +1,8 @@ +/** + * Contains data models for defining custom recipe behavior. + *

    + * Includes specialized Recipe subclasses such as crafting, cooking, + * brewing, and workstation recipes. These classes store configuration + * and behavior settings used by the recipe engine. + */ +package me.mehboss.utils.data; \ No newline at end of file diff --git a/src/me/mehboss/utils/Metrics.java b/src/me/mehboss/utils/libs/Metrics.java similarity index 99% rename from src/me/mehboss/utils/Metrics.java rename to src/me/mehboss/utils/libs/Metrics.java index e644ea3..cdaed4d 100644 --- a/src/me/mehboss/utils/Metrics.java +++ b/src/me/mehboss/utils/libs/Metrics.java @@ -1,4 +1,4 @@ -package me.mehboss.utils; +package me.mehboss.utils.libs; /* * This Metrics class was auto-generated and can be copied into your project if you are diff --git a/src/me/mehboss/utils/UpdateChecker.java b/src/me/mehboss/utils/libs/UpdateChecker.java similarity index 94% rename from src/me/mehboss/utils/UpdateChecker.java rename to src/me/mehboss/utils/libs/UpdateChecker.java index d4207eb..241c9f9 100644 --- a/src/me/mehboss/utils/UpdateChecker.java +++ b/src/me/mehboss/utils/libs/UpdateChecker.java @@ -1,36 +1,36 @@ -package me.mehboss.utils; - -import org.bukkit.Bukkit; -import org.bukkit.plugin.java.JavaPlugin; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.Scanner; -import java.util.function.Consumer; - -// From: https://www.spigotmc.org/wiki/creating-an-update-checker-that-checks-for-updates -public class UpdateChecker { - - private final JavaPlugin plugin; - private final int resourceId; - - public UpdateChecker(JavaPlugin plugin, int resourceId) { - this.plugin = plugin; - this.resourceId = resourceId; - } - - public void getVersion(final Consumer consumer) { - Bukkit.getScheduler().runTaskAsynchronously(this.plugin, () -> { - try (InputStream inputStream = new URL( - "https://api.spigotmc.org/legacy/update.php?resource=" + this.resourceId).openStream(); - Scanner scanner = new Scanner(inputStream)) { - if (scanner.hasNext()) { - consumer.accept(scanner.next()); - } - } catch (IOException exception) { - plugin.getLogger().info("Unable to check for updates: " + exception.getMessage()); - } - }); - } -} +package me.mehboss.utils.libs; + +import org.bukkit.Bukkit; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Scanner; +import java.util.function.Consumer; + +// From: https://www.spigotmc.org/wiki/creating-an-update-checker-that-checks-for-updates +public class UpdateChecker { + + private final JavaPlugin plugin; + private final int resourceId; + + public UpdateChecker(JavaPlugin plugin, int resourceId) { + this.plugin = plugin; + this.resourceId = resourceId; + } + + public void getVersion(final Consumer consumer) { + Bukkit.getScheduler().runTaskAsynchronously(this.plugin, () -> { + try (InputStream inputStream = new URL( + "https://api.spigotmc.org/legacy/update.php?resource=" + this.resourceId).openStream(); + Scanner scanner = new Scanner(inputStream)) { + if (scanner.hasNext()) { + consumer.accept(scanner.next()); + } + } catch (IOException exception) { + plugin.getLogger().info("Unable to check for updates: " + exception.getMessage()); + } + }); + } +} diff --git a/src/me/mehboss/utils/package-info.java b/src/me/mehboss/utils/package-info.java new file mode 100644 index 0000000..764eb8b --- /dev/null +++ b/src/me/mehboss/utils/package-info.java @@ -0,0 +1,8 @@ +/** + * Contains general-purpose utility classes used throughout the plugin. + *

    + * Includes item comparison logic, compatibility helpers, cooldown + * management, inventory utilities, meta checks, and shared helper + * functions used by multiple recipe systems. + */ +package me.mehboss.utils; \ No newline at end of file