Skip to content

Commit 4f5eab4

Browse files
authored
Develop (#35)
* Feat/improve (#25) * Feat/improve (#27) * fix: ia hook (#23) * feat: improve * feat: remove some useless usage of variable * Hotfix/air recipe (#30) * fix: ia hook (#23) * Feat/improve (#25) * Feat/improve (#27) * fix: ia hook (#23) * feat: improve * feat: remove some useless usage of variable * fix: air in recipe doesn't work * Fix/result not appear (#33) * fix: ia hook (#23) * Feat/improve (#25) * Feat/improve (#27) * fix: ia hook (#23) * feat: improve * feat: remove some useless usage of variable * version 3.0.0 (#32) * Feat/improve (#25) * Feat/improve (#27) * fix: ia hook (#23) * feat: improve * feat: remove some useless usage of variable * Hotfix/air recipe (#30) * fix: ia hook (#23) * Feat/improve (#25) * Feat/improve (#27) * fix: ia hook (#23) * feat: improve * feat: remove some useless usage of variable * fix: air in recipe doesn't work * Feat/modernize (#31) * feat: modernize * feat: version * fix: shapeless craft * fix: set handler highest and add result manually * fix: remove useless properties * feat: java version * feat: improve jitpack * Feat/player dynamic (#34) * fix: ia hook (#23) * Feat/improve (#25) * Feat/improve (#27) * fix: ia hook (#23) * feat: improve * feat: remove some useless usage of variable * version 3.0.0 (#32) * Feat/improve (#25) * Feat/improve (#27) * fix: ia hook (#23) * feat: improve * feat: remove some useless usage of variable * Hotfix/air recipe (#30) * fix: ia hook (#23) * Feat/improve (#25) * Feat/improve (#27) * fix: ia hook (#23) * feat: improve * feat: remove some useless usage of variable * fix: air in recipe doesn't work * Feat/modernize (#31) * feat: modernize * feat: version * fix: shapeless craft * fix: set handler highest and add result manually * fix: remove useless properties * feat: java version * feat: improve jitpack * feat: add dynamic player when it's possible during a craft * fix: compilation error * feat: develop * fix: import
1 parent 68445e9 commit 4f5eab4

14 files changed

Lines changed: 516 additions & 197 deletions

File tree

README.md

Lines changed: 224 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66

77
## Features
88
- **Create Custom Recipes**: Add shaped, shapeless, furnace, and other types of recipes with ease.
9+
- **Recipe Priority System**: Control recipe registration order to handle conflicting recipes (higher priority = registered first).
910
- **Advanced Recipe Handling**: Support for custom ingredients with metadata (lore, custom model data, persistent data container).
11+
- **Strict Ingredient Matching**: Option to require exact item matches including all metadata for precise recipe control.
1012
- **Easy Integration**: Simple API to integrate into any Spigot plugin.
1113
- **Plugin Hooks**: Built-in support for ItemsAdder and Oraxen items. You can create your own hook with your custom item systems.
1214
- **Version Compatibility**: Works with recent Spigot versions and allows you to create recipes dynamically.
@@ -90,9 +92,19 @@ public final class TestPlugin extends JavaPlugin {
9092
.setName("example-custom-ingredient")
9193
.setResult(new ItemStack(Material.DIAMOND))
9294
.setAmount(64)
93-
.addIngredient(magicPaper)
95+
.addIngredient(magicPaper, false) // false = normal matching (lore must match)
9496
.build();
9597

98+
// 3b. Same recipe but with strict matching (exact item required)
99+
ItemRecipe recipe3strict = new RecipeBuilder()
100+
.setType(RecipeType.CRAFTING_SHAPELESS)
101+
.setName("example-custom-ingredient-strict")
102+
.setResult(new ItemStack(Material.EMERALD))
103+
.setAmount(32)
104+
.addIngredient(magicPaper, true) // true = strict matching (all metadata must match)
105+
.setPriority(10) // Higher priority than normal recipes
106+
.build();
107+
96108
// 4. Furnace smelting recipe with cooking time and experience
97109
ItemRecipe recipe4 = new RecipeBuilder()
98110
.setType(RecipeType.SMELTING)
@@ -108,6 +120,7 @@ public final class TestPlugin extends JavaPlugin {
108120
recipesAPI.addRecipe(recipe1);
109121
recipesAPI.addRecipe(recipe2);
110122
recipesAPI.addRecipe(recipe3);
123+
recipesAPI.addRecipe(recipe3strict);
111124
recipesAPI.addRecipe(recipe4);
112125
}
113126
}
@@ -176,6 +189,37 @@ The API supports several types of ingredients:
176189
- **Tag**: Minecraft tags (e.g., planks, logs, wool)
177190
- **Plugin Items**: ItemsAdder and Oraxen custom items
178191

192+
<<<<<<< HEAD
193+
### Ingredient Matching Modes
194+
195+
**Normal Mode** (default):
196+
- Only checks metadata that is present in the recipe ingredient
197+
- Player can add extra metadata without breaking the recipe
198+
- Display name is NOT checked (players can rename items)
199+
- Lore, Custom Model Data, and PDC keys present in ingredient must match
200+
201+
**Strict Mode** (`strict: true`):
202+
- Requires exact match using Bukkit's `ItemStack.isSimilar()`
203+
- All metadata must match exactly
204+
- Use this when you need precise ingredient control
205+
206+
Example in code:
207+
```java
208+
// Normal mode - flexible matching
209+
.addIngredient(customItem, 'C', false)
210+
211+
// Strict mode - exact matching
212+
.addIngredient(customItem, 'C', true)
213+
```
214+
215+
Example in YAML:
216+
```yaml
217+
ingredients:
218+
- item: item:COBBLESTONE
219+
sign: 'C'
220+
strict: true # Requires exact match
221+
```
222+
179223
### Important Notes
180224
- **Display Name**: Player can rename items - only lore, custom model data, and PDC are checked
181225
- **Strict Mode**: Use `.addIngredient(item, sign, true)` to require exact match including display name
@@ -291,6 +335,7 @@ category: "MISC"
291335
- `category` - Recipe category (BUILDING, REDSTONE, EQUIPMENT, MISC for crafting; FOOD, BLOCKS, MISC for cooking)
292336
- `cooking-time` - Cooking time in ticks for smelting recipes (default: 0)
293337
- `experience` - Experience reward for smelting recipes (default: 0.0)
338+
- `priority` - Recipe registration priority (default: 0, higher = registered first)
294339

295340
### Pattern Validation
296341

@@ -301,6 +346,62 @@ For `CRAFTING_SHAPED` recipes, the pattern is validated:
301346
- Empty rows are not allowed
302347

303348
### Ingredient Types in YAML
349+
350+
#### Basic Format
351+
```yaml
352+
ingredients:
353+
- item: <type>:<value>
354+
sign: 'X' # Optional: Required for shaped recipes
355+
strict: true # Optional: Enable strict matching (default: false)
356+
```
357+
358+
#### Supported Types
359+
360+
- **Simple Material** (auto-detected):
361+
```yaml
362+
- item: DIAMOND # No prefix = Material
363+
```
364+
365+
- **Explicit Material** (same as above):
366+
```yaml
367+
- item: material:DIAMOND
368+
```
369+
370+
- **ItemStack from Material** (with metadata support):
371+
```yaml
372+
- item: item:COBBLESTONE
373+
strict: true # Recommended for items with metadata
374+
```
375+
Creates an ItemStack that can have metadata (lore, custom model data, PDC)
376+
377+
- **ItemStack from Base64**:
378+
```yaml
379+
- item: base64:BASE64_ENCODED_ITEM_STRING
380+
strict: true # Recommended for custom items
381+
```
382+
Load a custom item from a serialized Base64 string
383+
384+
- **Minecraft Tag**:
385+
```yaml
386+
- item: tag:planks # Accepts any plank type
387+
```
388+
389+
- **ItemsAdder Item**:
390+
```yaml
391+
- item: itemsadder:custom_item_id
392+
```
393+
394+
- **Oraxen Item**:
395+
```yaml
396+
- item: oraxen:custom_item_id
397+
```
398+
399+
- **Custom Plugin Hook**:
400+
```yaml
401+
- item: yourplugin:custom_item_id
402+
```
403+
404+
#### Field Details
304405
- `item: MATERIAL_NAME` - Simple material
305406
- `item: material:MATERIAL_NAME` - Explicit material
306407
- `item: tag:TAG_NAME` - Minecraft tag
@@ -329,13 +430,134 @@ category: MISC
329430
```yaml
330431
type: CRAFTING_SHAPELESS
331432
ingredients:
332-
- item: item:BASE64_ENCODED_ITEM_HERE
433+
- item: base64:BASE64_ENCODED_ITEM_HERE
333434
strict: true
334435
result:
335436
item: DIAMOND
336437
amount: 1
337438
```
338439

440+
## Recipe Priority System
441+
442+
When multiple recipes have similar ingredients, the **priority** field determines which recipe is checked first. This is crucial for handling conflicting recipes.
443+
444+
### How Priority Works
445+
- Recipes are sorted by priority before registration (higher priority = registered first)
446+
- Default priority is `0`
447+
- Higher priority recipes are checked before lower priority ones
448+
- Useful for specific recipes that should take precedence over generic ones
449+
450+
### Example Use Case: Compressed Cobblestone
451+
452+
```yaml
453+
# cobblestone_to_compressed_x1.yml
454+
type: CRAFTING_SHAPED
455+
priority: 0 # Lower priority (default)
456+
pattern:
457+
- "CCC"
458+
- "CCC"
459+
- "CCC"
460+
ingredients:
461+
- item: material:COBBLESTONE
462+
sign: 'C'
463+
result:
464+
item: yourplugin:compressed_cobblestone_x1
465+
amount: 1
466+
```
467+
468+
```yaml
469+
# compressed_x1_to_x2.yml
470+
type: CRAFTING_SHAPED
471+
priority: 10 # Higher priority - checked first!
472+
pattern:
473+
- "CCC"
474+
- "CCC"
475+
- "CCC"
476+
ingredients:
477+
- item: item:COMPRESSED_COBBLESTONE_X1 # Custom item with metadata
478+
sign: 'C'
479+
strict: true # Important: ensures exact match
480+
result:
481+
item: yourplugin:compressed_cobblestone_x2
482+
amount: 1
483+
```
484+
485+
In this example, the x1→x2 recipe will be checked first because it has `priority: 10`. The `strict: true` flag ensures that only compressed x1 cobblestone (not regular cobblestone) triggers this recipe.
486+
487+
### Priority in Code
488+
489+
```java
490+
ItemRecipe highPriorityRecipe = new RecipeBuilder()
491+
.setType(RecipeType.CRAFTING_SHAPED)
492+
.setName("specific-recipe")
493+
.setPriority(10) // Higher priority
494+
.setPattern("AAA", "AAA", "AAA")
495+
.addIngredient(specificItem, 'A', true) // Strict matching
496+
.setResult(new ItemStack(Material.DIAMOND))
497+
.build();
498+
499+
ItemRecipe lowPriorityRecipe = new RecipeBuilder()
500+
.setType(RecipeType.CRAFTING_SHAPED)
501+
.setName("generic-recipe")
502+
.setPriority(0) // Default priority
503+
.setPattern("AAA", "AAA", "AAA")
504+
.addIngredient(Material.STONE, 'A')
505+
.setResult(new ItemStack(Material.COAL))
506+
.build();
507+
```
508+
509+
## Parsing Ingredients Programmatically
510+
511+
RecipesAPI provides a public utility method to parse ingredients from strings, useful for loading recipes from custom sources or configuration files.
512+
513+
### Using Util.parseIngredient()
514+
515+
```java
516+
import fr.traqueur.recipes.api.Util;
517+
import fr.traqueur.recipes.api.domains.Ingredient;
518+
519+
// Parse a simple material
520+
Ingredient diamond = Util.parseIngredient("DIAMOND");
521+
522+
// Parse with a sign for shaped recipes
523+
Ingredient stone = Util.parseIngredient("item:STONE", 'S');
524+
525+
// Parse with strict mode enabled
526+
Ingredient customItem = Util.parseIngredient("item:COBBLESTONE", 'C', true);
527+
528+
// Parse from base64
529+
Ingredient base64Item = Util.parseIngredient("base64:YOUR_BASE64_STRING", null, true);
530+
531+
// Parse from tags
532+
Ingredient planks = Util.parseIngredient("tag:planks", 'P');
533+
534+
// Parse from plugin items
535+
Ingredient iaItem = Util.parseIngredient("itemsadder:custom_item", 'I');
536+
Ingredient oraxenItem = Util.parseIngredient("oraxen:custom_sword", 'S');
537+
```
538+
539+
### Method Signatures
540+
541+
```java
542+
// Full control
543+
public static Ingredient parseIngredient(String itemString, Character sign, boolean strict)
544+
545+
// Without strict mode (default: false)
546+
public static Ingredient parseIngredient(String itemString, Character sign)
547+
548+
// Shapeless recipe (no sign)
549+
public static Ingredient parseIngredient(String itemString)
550+
```
551+
552+
### Supported Formats
553+
All formats supported in YAML are also supported here:
554+
- `"MATERIAL_NAME"` → MaterialIngredient
555+
- `"material:MATERIAL_NAME"` → MaterialIngredient
556+
- `"item:MATERIAL_NAME"` → ItemStackIngredient (supports metadata)
557+
- `"base64:BASE64_STRING"` → ItemStackIngredient from serialized item
558+
- `"tag:TAG_NAME"` → TagIngredient
559+
- `"pluginname:item_id"` → Custom plugin hook
560+
339561
## Resources
340562

341563
- **Javadoc**: [API Documentation](https://jitpack.io/com/github/Traqueur-dev/RecipesAPI/latest/javadoc/)

build.gradle

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ repositories {
2424
}
2525

2626
dependencies {
27-
compileOnly "org.spigotmc:spigot-api:1.21.3-R0.1-SNAPSHOT"
27+
compileOnly "org.spigotmc:spigot-api:1.21.4-R0.1-SNAPSHOT"
2828

2929
// Hooks
3030
compileOnly 'io.th0rgal:oraxen:1.181.0'
@@ -44,6 +44,8 @@ tasks.register('generateVersionProperties') {
4444
processResources.dependsOn generateVersionProperties
4545

4646
java {
47+
sourceCompatibility = JavaVersion.VERSION_21
48+
targetCompatibility = JavaVersion.VERSION_21
4749
withSourcesJar()
4850
withJavadocJar()
4951
}

jitpack.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
jdk:
2-
- openjdk21
2+
- openjdk21
3+
install:
4+
- ./gradlew clean publishToMavenLocal -xtest --no-daemon --console=plain

src/main/java/fr/traqueur/recipes/api/RecipeLoader.java

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -149,22 +149,28 @@ private void extractDefaultsFromJar(String jarPath) {
149149
* @return The number of recipes loaded
150150
*/
151151
public int load() {
152-
int count = 0;
152+
List<ItemRecipe> recipes = new ArrayList<>();
153153

154154
// Load from folders
155155
for (File folder : folders) {
156-
count += loadFromFolder(folder);
156+
loadFromFolder(folder, recipes);
157157
}
158158

159159
// Load from individual files
160160
for (File file : files) {
161-
if (loadRecipe(file)) {
162-
count++;
163-
}
161+
loadRecipe(file, recipes);
162+
}
163+
164+
// Sort recipes by priority (higher priority first)
165+
recipes.sort((r1, r2) -> Integer.compare(r2.priority(), r1.priority()));
166+
167+
// Register sorted recipes
168+
for (ItemRecipe recipe : recipes) {
169+
api.addRecipe(recipe);
164170
}
165171

166-
plugin.getLogger().info("Loaded " + count + " recipes via RecipeLoader.");
167-
return count;
172+
plugin.getLogger().info("Loaded " + recipes.size() + " recipes via RecipeLoader.");
173+
return recipes.size();
168174
}
169175

170176
/**
@@ -180,45 +186,39 @@ public int reload() {
180186
/**
181187
* Load all recipes from a folder (recursive)
182188
* @param folder The folder to load recipes from
183-
* @return The number of recipes loaded
189+
* @param recipes The list to add loaded recipes to
184190
*/
185-
private int loadFromFolder(File folder) {
186-
int count = 0;
191+
private void loadFromFolder(File folder, List<ItemRecipe> recipes) {
187192
try (Stream<Path> stream = Files.walk(folder.toPath())) {
188193
List<File> ymlFiles = stream.map(Path::toFile)
189194
.filter(File::isFile)
190195
.filter(f -> f.getName().endsWith(".yml"))
191196
.toList();
192197

193198
for (File file : ymlFiles) {
194-
if (loadRecipe(file)) {
195-
count++;
196-
}
199+
loadRecipe(file, recipes);
197200
}
198201
} catch (IOException exception) {
199202
plugin.getLogger().severe("Could not load recipes from folder " + folder.getAbsolutePath() + ": " + exception.getMessage());
200203
}
201-
return count;
202204
}
203205

204206
/**
205207
* Load a recipe from a file
206208
* @param file The file to load the recipe from
207-
* @return true if the recipe was loaded successfully, false otherwise
209+
* @param recipes The list to add the loaded recipe to
208210
*/
209-
private boolean loadRecipe(File file) {
211+
private void loadRecipe(File file, List<ItemRecipe> recipes) {
210212
try {
211213
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(file);
212214
ItemRecipe recipe = new RecipeConfiguration(file.getName().replace(".yml", ""), configuration)
213215
.build();
214-
api.addRecipe(recipe);
215-
return true;
216+
recipes.add(recipe);
216217
} catch (Exception e) {
217218
plugin.getLogger().severe("Could not load recipe from file " + file.getAbsolutePath() + ": " + e.getMessage());
218219
if (api.isDebug()) {
219220
e.printStackTrace();
220221
}
221-
return false;
222222
}
223223
}
224224
}

0 commit comments

Comments
 (0)