├── .github
└── workflows
│ └── gradle.yml
├── .gitignore
├── LICENSE
├── README.md
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── jitpack.yml
├── settings.gradle
├── src
└── main
│ ├── java
│ └── fr
│ │ └── traqueur
│ │ └── recipes
│ │ ├── api
│ │ ├── Base64.java
│ │ ├── RecipeType.java
│ │ ├── RecipesAPI.java
│ │ ├── TagRegistry.java
│ │ ├── domains
│ │ │ ├── Ingredient.java
│ │ │ └── Recipe.java
│ │ └── hook
│ │ │ └── Hook.java
│ │ └── impl
│ │ ├── PrepareCraftListener.java
│ │ ├── domains
│ │ ├── ItemRecipe.java
│ │ ├── ingredients
│ │ │ ├── ItemStackIngredient.java
│ │ │ ├── MaterialIngredient.java
│ │ │ ├── StrictItemStackIngredient.java
│ │ │ └── TagIngredient.java
│ │ └── recipes
│ │ │ ├── RecipeBuilder.java
│ │ │ └── RecipeConfiguration.java
│ │ ├── hook
│ │ ├── Hooks.java
│ │ └── hooks
│ │ │ ├── ItemsAdderIngredient.java
│ │ │ └── OraxenIngredient.java
│ │ └── updater
│ │ └── Updater.java
│ └── resources
│ └── version.properties
└── test-plugin
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── src
└── main
├── java
└── fr
│ └── traqueur
│ └── testplugin
│ └── TestPlugin.java
└── resources
├── plugin.yml
└── recipes
└── example.yml
/.github/workflows/gradle.yml:
--------------------------------------------------------------------------------
1 | # This workflow uses actions that are not certified by GitHub.
2 | # They are provided by a third-party and are governed by
3 | # separate terms of service, privacy policy, and support
4 | # documentation.
5 | # This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
6 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle
7 |
8 | name: Java CI with Gradle
9 |
10 | on:
11 | push:
12 | branches: [ "main" ]
13 | pull_request:
14 | branches: [ "main" ]
15 |
16 | jobs:
17 | build:
18 |
19 | runs-on: ubuntu-latest
20 | permissions:
21 | contents: read
22 |
23 | steps:
24 | - uses: actions/checkout@v4
25 | - name: Set up JDK 21
26 | uses: actions/setup-java@v4
27 | with:
28 | java-version: '21'
29 | distribution: 'temurin'
30 |
31 | # Configure Gradle for optimal use in GitHub Actions, including caching of downloaded dependencies.
32 | # See: https://github.com/gradle/actions/blob/main/setup-gradle/README.md
33 | - name: Setup Gradle
34 | uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0
35 | - name: Grant Access
36 | run: chmod +x ./gradlew
37 | - name: Build with Gradle Wrapper
38 | run: ./gradlew build
39 |
40 | # NOTE: The Gradle Wrapper is the default and recommended way to run Gradle (https://docs.gradle.org/current/userguide/gradle_wrapper.html).
41 | # If your project does not have the Gradle Wrapper configured, you can use the following configuration to run Gradle with a specified version.
42 | #
43 | # - name: Setup Gradle
44 | # uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0
45 | # with:
46 | # gradle-version: '8.9'
47 | #
48 | # - name: Build with Gradle 8.9
49 | # run: gradle build
50 |
51 | dependency-submission:
52 |
53 | runs-on: ubuntu-latest
54 | permissions:
55 | contents: write
56 |
57 | steps:
58 | - uses: actions/checkout@v4
59 | - name: Set up JDK 21
60 | uses: actions/setup-java@v4
61 | with:
62 | java-version: '21'
63 | distribution: 'temurin'
64 |
65 | # Generates and submits a dependency graph, enabling Dependabot Alerts for all project dependencies.
66 | # See: https://github.com/gradle/actions/blob/main/dependency-submission/README.md
67 | - name: Generate and submit dependency graph
68 | uses: gradle/actions/dependency-submission@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0
69 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # User-specific stuff
2 | .idea/
3 |
4 | *.iml
5 | *.ipr
6 | *.iws
7 |
8 | # IntelliJ
9 | out/
10 | # mpeltonen/sbt-idea plugin
11 | .idea_modules/
12 |
13 | # JIRA plugin
14 | atlassian-ide-plugin.xml
15 |
16 | # Compiled class file
17 | *.class
18 |
19 | # Log file
20 | *.log
21 |
22 | # BlueJ files
23 | *.ctxt
24 |
25 | # Package Files #
26 | *.jar
27 | *.war
28 | *.nar
29 | *.ear
30 | *.zip
31 | *.tar.gz
32 | *.rar
33 |
34 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
35 | hs_err_pid*
36 |
37 | *~
38 |
39 | # temporary files which can be created if a process still has a handle open of a deleted file
40 | .fuse_hidden*
41 |
42 | # KDE directory preferences
43 | .directory
44 |
45 | # Linux trash folder which might appear on any partition or disk
46 | .Trash-*
47 |
48 | # .nfs files are created when an open file is removed but is still being accessed
49 | .nfs*
50 |
51 | # General
52 | .DS_Store
53 | .AppleDouble
54 | .LSOverride
55 |
56 | # Icon must end with two \r
57 | Icon
58 |
59 | # Thumbnails
60 | ._*
61 |
62 | # Files that might appear in the root of a volume
63 | .DocumentRevisions-V100
64 | .fseventsd
65 | .Spotlight-V100
66 | .TemporaryItems
67 | .Trashes
68 | .VolumeIcon.icns
69 | .com.apple.timemachine.donotpresent
70 |
71 | # Directories potentially created on remote AFP share
72 | .AppleDB
73 | .AppleDesktop
74 | Network Trash Folder
75 | Temporary Items
76 | .apdisk
77 |
78 | # Windows thumbnail cache files
79 | Thumbs.db
80 | Thumbs.db:encryptable
81 | ehthumbs.db
82 | ehthumbs_vista.db
83 |
84 | # Dump file
85 | *.stackdump
86 |
87 | # Folder config file
88 | [Dd]esktop.ini
89 |
90 | # Recycle Bin used on file shares
91 | $RECYCLE.BIN/
92 |
93 | # Windows Installer files
94 | *.cab
95 | *.msi
96 | *.msix
97 | *.msm
98 | *.msp
99 |
100 | # Windows shortcuts
101 | *.lnk
102 |
103 | .gradle
104 | build/
105 |
106 | # Ignore Gradle GUI config
107 | gradle-app.setting
108 |
109 | # Cache of project
110 | .gradletasknamecache
111 |
112 | **/build/
113 |
114 | # Common working directory
115 | run/
116 |
117 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
118 | !gradle-wrapper.jar
119 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Traqueur
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # RecipesAPI
2 |
3 | 
4 |
5 | **RecipesAPI** is a lightweight and easy-to-use API that allows you to create custom recipes for your Spigot server. Whether you want to add custom shaped or shapeless crafting recipes, furnace smelting recipes, or other custom item interactions, this API makes it simple to do so.
6 |
7 | ## Features
8 | - **Create Custom Recipes**: Add shaped, shapeless, and furnace, and other type recipes with ease.
9 | - **Advanced Recipe Handling**: Support for custom ingredients with meta data (e.g., items with custom names).
10 | - **Easy Integration**: Simple API to integrate into any Spigot plugin.
11 | - **Hooks**: Support ItemsAdder, Oraxen items. You can create your own hook with your customs items systems.
12 | - **Version Compatibility**: Works with recent Spigot versions and allows you to create recipes dynamically. Folia compatibility if you use FoliaLib.
13 | - **Lightweight**: No need to include large libraries or dependencies.
14 | - **Open Source**: Available under the MIT License.
15 | - **Javadoc**: Comprehensive documentation for easy reference.
16 |
17 | ## Installation
18 |
19 | 0. **Prerequisites**:
20 | - Make sure you have **Java 21+** and **Spigot** installed on your system.
21 | 1. Add **RecipesAPI** to your project via **JitPack**. Add the following to your `build.gradle`:
22 |
23 | ```groovy
24 | repositories {
25 | maven { url 'https://jitpack.io' }
26 | }
27 |
28 | dependencies {
29 | implementation 'com.github.Traqueur-dev:RecipesAPI:VERSION'
30 | }
31 | ```
32 |
33 | 2. Since you are using **Shadow Plugin** for building, make sure to relocate the API to avoid conflicts:
34 |
35 | ```groovy
36 | shadowJar {
37 | relocate 'fr.traqueur.recipes', 'your.plugin.package.recipes'
38 | }
39 | ```
40 |
41 | 3. Build your plugin with `./gradlew build`.
42 |
43 | ## Usage Example
44 |
45 | Below is an example of how to use **RecipesAPI** in your Spigot plugin.
46 | This example demonstrates adding four types of recipes: a simple shapeless crafting recipe, a shaped crafting recipe, a custom ingredient shapeless recipe, and a furnace recipe.
47 | You can see how easy it is to create and register recipes with the API.
48 | The exemple plugin is available in the `test-plugin` directory.
49 |
50 | ```java
51 | public final class TestPlugin extends JavaPlugin {
52 |
53 | private RecipesAPI recipesAPI;
54 |
55 | @Override
56 | public void onEnable() {
57 | // Initialize RecipesAPI
58 | recipesAPI = new RecipesAPI(this, true);
59 |
60 | // Create a simple shapeless crafting recipe (DIRT -> 64 DIAMOND)
61 | ItemRecipe recipe = new RecipeBuilder()
62 | .setType(RecipeType.CRAFTING_SHAPELESS)
63 | .setName("example-simple")
64 | .setResult(new ItemStack(Material.DIAMOND))
65 | .setAmount(64)
66 | .addIngredient(Material.DIRT)
67 | .build();
68 |
69 | // Create a shaped crafting recipe (DIRT and DIAMOND -> 64 DIAMOND)
70 | ItemRecipe recipe2 = new RecipeBuilder()
71 | .setType(RecipeType.CRAFTING_SHAPED)
72 | .setName("example-shaped")
73 | .setResult(new ItemStack(Material.DIAMOND))
74 | .setAmount(64)
75 | .setPattern("DDD", "DID", "DDD")
76 | .addIngredient(Material.DIRT, 'D')
77 | .addIngredient(Material.DIAMOND, 'I')
78 | .build();
79 |
80 | // Create a shapeless recipe with a custom ingredient (named PAPER)
81 | ItemStack ingredient = new ItemStack(Material.PAPER);
82 | ItemMeta meta = ingredient.getItemMeta();
83 | meta.setDisplayName("Dirt Magic");
84 | ingredient.setItemMeta(meta);
85 |
86 | ItemRecipe recipe3 = new RecipeBuilder()
87 | .setType(RecipeType.CRAFTING_SHAPELESS)
88 | .setName("example-complex")
89 | .setResult(new ItemStack(Material.DIAMOND))
90 | .setAmount(64)
91 | .addIngredient(ingredient)
92 | .build();
93 |
94 | // Create a furnace smelting recipe (PAPER -> 64 DIAMOND)
95 | ItemRecipe recipe4 = new RecipeBuilder()
96 | .setType(RecipeType.SMELTING)
97 | .setName("example-furnace")
98 | .setResult(new ItemStack(Material.DIAMOND))
99 | .setAmount(64)
100 | .addIngredient(ingredient)
101 | .setCookingTime(10)
102 | .build();
103 |
104 | // Add the recipes to the API
105 | recipesAPI.addRecipe(recipe);
106 | recipesAPI.addRecipe(recipe2);
107 | recipesAPI.addRecipe(recipe3);
108 | recipesAPI.addRecipe(recipe4);
109 | }
110 | }
111 | ```
112 |
113 | ## How to Use
114 |
115 | - **Shapeless Recipe**: Add items to crafting in any arrangement.
116 | - **Shaped Recipe**: Define specific patterns for crafting items.
117 | - **Custom Ingredients**: Use items with custom names or metadata in recipes.
118 | - **Furnace Recipes**: Create custom smelting recipes with adjustable cooking time.
119 |
120 | ## API Documentation
121 | The API is simple and intuitive to use. You can easily:
122 | - **Define crafting types**: `RecipeType.CRAFTING_SHAPELESS`, `RecipeType.CRAFTING_SHAPED`,
123 | `RecipeType.SMELTING`, etc.
124 | - **Add ingredients**: Either regular materials or custom items with `ItemMeta`.
125 | - **Set crafting patterns**: For shaped recipes, you can define the crafting grid with `.setPattern()`.
126 | - **Control output**: Set the resulting item and amount.
127 |
128 | You can check javadoc here : [Javadoc](https://jitpack.io/com/github/Traqueur-dev/RecipesAPI/latest/javadoc/)
129 | You can check the wiki here : [Wiki](https://github.com/Traqueur-dev/RecipesAPI/wiki)
130 |
131 | ## License
132 | This project is licensed under the MIT License.
133 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'java'
3 | id 'maven-publish'
4 | }
5 |
6 | group = 'fr.traqueur'
7 | version = property("version")
8 |
9 | repositories {
10 | mavenCentral()
11 | maven {
12 | url = 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/'
13 | }
14 | maven {
15 | name = "sonatype"
16 | url = "https://oss.sonatype.org/content/groups/public/"
17 | }
18 | maven {
19 | name = "jitpack"
20 | url = "https://jitpack.io"
21 | }
22 | maven {
23 | url "https://repo.oraxen.com/releases"
24 | }
25 | }
26 |
27 | dependencies {
28 | compileOnly "org.spigotmc:spigot-api:1.21.3-R0.1-SNAPSHOT"
29 |
30 | // Hooks
31 | compileOnly 'io.th0rgal:oraxen:1.181.0'
32 | compileOnly 'com.github.LoneDev6:API-ItemsAdder:3.6.1'
33 | }
34 |
35 | tasks.register('generateVersionProperties') {
36 | doLast {
37 | def file = new File("$projectDir/src/main/resources/version.properties")
38 | if (!file.parentFile.exists()) {
39 | file.parentFile.mkdirs()
40 | }
41 | file.text = "version=${project.version}"
42 | }
43 | }
44 |
45 | processResources.dependsOn generateVersionProperties
46 |
47 | java {
48 | withSourcesJar()
49 | withJavadocJar()
50 | }
51 |
52 |
53 | publishing {
54 | publications {
55 | maven(MavenPublication) {
56 | from components.java
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | version=2.0.1
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Traqueur-dev/RecipesAPI/7f74390966352ab15bd5bc68c6d0552eaf6bbbf1/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | #
21 | # Gradle start up script for POSIX generated by Gradle.
22 | #
23 | # Important for running:
24 | #
25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
26 | # noncompliant, but you have some other compliant shell such as ksh or
27 | # bash, then to run this script, type that shell name before the whole
28 | # command line, like:
29 | #
30 | # ksh Gradle
31 | #
32 | # Busybox and similar reduced shells will NOT work, because this script
33 | # requires all of these POSIX shell features:
34 | # * functions;
35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
37 | # * compound commands having a testable exit status, especially «case»;
38 | # * various built-in commands including «command», «set», and «ulimit».
39 | #
40 | # Important for patching:
41 | #
42 | # (2) This script targets any POSIX shell, so it avoids extensions provided
43 | # by Bash, Ksh, etc; in particular arrays are avoided.
44 | #
45 | # The "traditional" practice of packing multiple parameters into a
46 | # space-separated string is a well documented source of bugs and security
47 | # problems, so this is (mostly) avoided, by progressively accumulating
48 | # options in "$@", and eventually passing that to Java.
49 | #
50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
52 | # see the in-line comments for details.
53 | #
54 | # There are tweaks for specific operating systems such as AIX, CygWin,
55 | # Darwin, MinGW, and NonStop.
56 | #
57 | # (3) This script is generated from the Groovy template
58 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
59 | # within the Gradle project.
60 | #
61 | # You can find Gradle at https://github.com/gradle/gradle/.
62 | #
63 | ##############################################################################
64 |
65 | # Attempt to set APP_HOME
66 |
67 | # Resolve links: $0 may be a link
68 | app_path=$0
69 |
70 | # Need this for daisy-chained symlinks.
71 | while
72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
73 | [ -h "$app_path" ]
74 | do
75 | ls=$( ls -ld "$app_path" )
76 | link=${ls#*' -> '}
77 | case $link in #(
78 | /*) app_path=$link ;; #(
79 | *) app_path=$APP_HOME$link ;;
80 | esac
81 | done
82 |
83 | # This is normally unused
84 | # shellcheck disable=SC2034
85 | APP_BASE_NAME=${0##*/}
86 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
87 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
88 |
89 | # Use the maximum available, or set MAX_FD != -1 to use that value.
90 | MAX_FD=maximum
91 |
92 | warn () {
93 | echo "$*"
94 | } >&2
95 |
96 | die () {
97 | echo
98 | echo "$*"
99 | echo
100 | exit 1
101 | } >&2
102 |
103 | # OS specific support (must be 'true' or 'false').
104 | cygwin=false
105 | msys=false
106 | darwin=false
107 | nonstop=false
108 | case "$( uname )" in #(
109 | CYGWIN* ) cygwin=true ;; #(
110 | Darwin* ) darwin=true ;; #(
111 | MSYS* | MINGW* ) msys=true ;; #(
112 | NONSTOP* ) nonstop=true ;;
113 | esac
114 |
115 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
116 |
117 |
118 | # Determine the Java command to use to start the JVM.
119 | if [ -n "$JAVA_HOME" ] ; then
120 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
121 | # IBM's JDK on AIX uses strange locations for the executables
122 | JAVACMD=$JAVA_HOME/jre/sh/java
123 | else
124 | JAVACMD=$JAVA_HOME/bin/java
125 | fi
126 | if [ ! -x "$JAVACMD" ] ; then
127 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
128 |
129 | Please set the JAVA_HOME variable in your environment to match the
130 | location of your Java installation."
131 | fi
132 | else
133 | JAVACMD=java
134 | if ! command -v java >/dev/null 2>&1
135 | then
136 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
137 |
138 | Please set the JAVA_HOME variable in your environment to match the
139 | location of your Java installation."
140 | fi
141 | fi
142 |
143 | # Increase the maximum file descriptors if we can.
144 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
145 | case $MAX_FD in #(
146 | max*)
147 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
148 | # shellcheck disable=SC2039,SC3045
149 | MAX_FD=$( ulimit -H -n ) ||
150 | warn "Could not query maximum file descriptor limit"
151 | esac
152 | case $MAX_FD in #(
153 | '' | soft) :;; #(
154 | *)
155 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
156 | # shellcheck disable=SC2039,SC3045
157 | ulimit -n "$MAX_FD" ||
158 | warn "Could not set maximum file descriptor limit to $MAX_FD"
159 | esac
160 | fi
161 |
162 | # Collect all arguments for the java command, stacking in reverse order:
163 | # * args from the command line
164 | # * the main class name
165 | # * -classpath
166 | # * -D...appname settings
167 | # * --module-path (only if needed)
168 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
169 |
170 | # For Cygwin or MSYS, switch paths to Windows format before running java
171 | if "$cygwin" || "$msys" ; then
172 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
173 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
174 |
175 | JAVACMD=$( cygpath --unix "$JAVACMD" )
176 |
177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
178 | for arg do
179 | if
180 | case $arg in #(
181 | -*) false ;; # don't mess with options #(
182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
183 | [ -e "$t" ] ;; #(
184 | *) false ;;
185 | esac
186 | then
187 | arg=$( cygpath --path --ignore --mixed "$arg" )
188 | fi
189 | # Roll the args list around exactly as many times as the number of
190 | # args, so each arg winds up back in the position where it started, but
191 | # possibly modified.
192 | #
193 | # NB: a `for` loop captures its iteration list before it begins, so
194 | # changing the positional parameters here affects neither the number of
195 | # iterations, nor the values presented in `arg`.
196 | shift # remove old arg
197 | set -- "$@" "$arg" # push replacement arg
198 | done
199 | fi
200 |
201 |
202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
204 |
205 | # Collect all arguments for the java command:
206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
207 | # and any embedded shellness will be escaped.
208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
209 | # treated as '${Hostname}' itself on the command line.
210 |
211 | set -- \
212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
213 | -classpath "$CLASSPATH" \
214 | org.gradle.wrapper.GradleWrapperMain \
215 | "$@"
216 |
217 | # Stop when "xargs" is not available.
218 | if ! command -v xargs >/dev/null 2>&1
219 | then
220 | die "xargs is not available"
221 | fi
222 |
223 | # Use "xargs" to parse quoted args.
224 | #
225 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
226 | #
227 | # In Bash we could simply go:
228 | #
229 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
230 | # set -- "${ARGS[@]}" "$@"
231 | #
232 | # but POSIX shell has neither arrays nor command substitution, so instead we
233 | # post-process each arg (as a line of input to sed) to backslash-escape any
234 | # character that might be a shell metacharacter, then use eval to reverse
235 | # that process (while maintaining the separation between arguments), and wrap
236 | # the whole thing up as a single "set" statement.
237 | #
238 | # This will of course break if any of these variables contains a newline or
239 | # an unmatched quote.
240 | #
241 |
242 | eval "set -- $(
243 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
244 | xargs -n1 |
245 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
246 | tr '\n' ' '
247 | )" '"$@"'
248 |
249 | exec "$JAVACMD" "$@"
250 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%"=="" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%"=="" set DIRNAME=.
29 | @rem This is normally unused
30 | set APP_BASE_NAME=%~n0
31 | set APP_HOME=%DIRNAME%
32 |
33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
35 |
36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
38 |
39 | @rem Find java.exe
40 | if defined JAVA_HOME goto findJavaFromJavaHome
41 |
42 | set JAVA_EXE=java.exe
43 | %JAVA_EXE% -version >NUL 2>&1
44 | if %ERRORLEVEL% equ 0 goto execute
45 |
46 | echo. 1>&2
47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
48 | echo. 1>&2
49 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
50 | echo location of your Java installation. 1>&2
51 |
52 | goto fail
53 |
54 | :findJavaFromJavaHome
55 | set JAVA_HOME=%JAVA_HOME:"=%
56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
57 |
58 | if exist "%JAVA_EXE%" goto execute
59 |
60 | echo. 1>&2
61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
62 | echo. 1>&2
63 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
64 | echo location of your Java installation. 1>&2
65 |
66 | goto fail
67 |
68 | :execute
69 | @rem Setup the command line
70 |
71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
72 |
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if %ERRORLEVEL% equ 0 goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | set EXIT_CODE=%ERRORLEVEL%
85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
87 | exit /b %EXIT_CODE%
88 |
89 | :mainEnd
90 | if "%OS%"=="Windows_NT" endlocal
91 |
92 | :omega
93 |
--------------------------------------------------------------------------------
/jitpack.yml:
--------------------------------------------------------------------------------
1 | jdk:
2 | - openjdk21
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'RecipesAPI'
2 |
3 | include ':test-plugin'
4 |
--------------------------------------------------------------------------------
/src/main/java/fr/traqueur/recipes/api/Base64.java:
--------------------------------------------------------------------------------
1 | package fr.traqueur.recipes.api;
2 |
3 | import org.bukkit.inventory.ItemStack;
4 | import org.bukkit.util.io.BukkitObjectInputStream;
5 | import org.bukkit.util.io.BukkitObjectOutputStream;
6 |
7 | import java.io.*;
8 | import java.util.zip.GZIPInputStream;
9 | import java.util.zip.GZIPOutputStream;
10 |
11 | /**
12 | * A class that provides Base64 encoding and decoding as defined by RFC 2045
13 | * This class is used to encode and decode to and from Base64 in a way
14 | * This class from zEssentials project coded by Maxlego08
15 | * Link of his project
16 | */
17 | public final class Base64 {
18 |
19 | static private final int BASELENGTH = 128;
20 | static private final int LOOKUPLENGTH = 64;
21 | static private final int TWENTYFOURBITGROUP = 24;
22 | static private final int EIGHTBIT = 8;
23 | static private final int SIXTEENBIT = 16;
24 | static private final int FOURBYTE = 4;
25 | static private final int SIGN = -128;
26 | static private final char PAD = '=';
27 | static private final boolean fDebug = false;
28 | static final private byte [] base64Alphabet = new byte[BASELENGTH];
29 | static final private char [] lookUpBase64Alphabet = new char[LOOKUPLENGTH];
30 |
31 | static {
32 |
33 | for (int i = 0; i < BASELENGTH; ++i) {
34 | base64Alphabet[i] = -1;
35 | }
36 | for (int i = 'Z'; i >= 'A'; i--) {
37 | base64Alphabet[i] = (byte) (i-'A');
38 | }
39 | for (int i = 'z'; i>= 'a'; i--) {
40 | base64Alphabet[i] = (byte) ( i-'a' + 26);
41 | }
42 |
43 | for (int i = '9'; i >= '0'; i--) {
44 | base64Alphabet[i] = (byte) (i-'0' + 52);
45 | }
46 |
47 | base64Alphabet['+'] = 62;
48 | base64Alphabet['/'] = 63;
49 |
50 | for (int i = 0; i<=25; i++)
51 | lookUpBase64Alphabet[i] = (char)('A'+i);
52 |
53 | for (int i = 26, j = 0; i<=51; i++, j++)
54 | lookUpBase64Alphabet[i] = (char)('a'+ j);
55 |
56 | for (int i = 52, j = 0; i<=61; i++, j++)
57 | lookUpBase64Alphabet[i] = (char)('0' + j);
58 | lookUpBase64Alphabet[62] = (char)'+';
59 | lookUpBase64Alphabet[63] = (char)'/';
60 |
61 | }
62 |
63 | /**
64 | * Encodes a itemstack into Base64
65 | *
66 | * @param item String to encode
67 | * @return Encoded Base64 string
68 | */
69 | public static String encodeItem(ItemStack item) {
70 | try {
71 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
72 | GZIPOutputStream gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream);
73 | ObjectOutputStream objectOutputStream = new BukkitObjectOutputStream(gzipOutputStream);
74 | objectOutputStream.writeObject(item);
75 | objectOutputStream.close();
76 | return Base64.encode(byteArrayOutputStream.toByteArray());
77 | } catch (IOException exception) {
78 | exception.printStackTrace();
79 | return null;
80 | }
81 | }
82 |
83 | /**
84 | * Decodes a Base64 string into a itemstack
85 | *
86 | * @param data String to decode
87 | * @return Decoded itemstack
88 | */
89 | public static ItemStack decodeItem(String data) {
90 | try {
91 | ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(Base64.decode(data));
92 | GZIPInputStream gzipInputStream = new GZIPInputStream(byteArrayInputStream);
93 | ObjectInputStream objectInputStream = new BukkitObjectInputStream(gzipInputStream);
94 | ItemStack item = (ItemStack) objectInputStream.readObject();
95 | objectInputStream.close();
96 | return item;
97 | } catch (IOException | ClassNotFoundException exception) {
98 | exception.printStackTrace();
99 | return null;
100 | }
101 | }
102 |
103 |
104 | /**
105 | * Check if octect is whitespace
106 | *
107 | * @param octect The octet to check
108 | * @return True if the octect is whitespace, false otherwise
109 | */
110 | protected static boolean isWhiteSpace(char octect) {
111 | return (octect == 0x20 || octect == 0xd || octect == 0xa || octect == 0x9);
112 | }
113 |
114 | /**
115 | * Check if octect is a pad
116 | *
117 | * @param octect The octet to check
118 | * @return True if the octect is a pad, false otherwise
119 | */
120 | protected static boolean isPad(char octect) {
121 | return (octect == PAD);
122 | }
123 |
124 | /**
125 | * Check if octect is data
126 | *
127 | * @param octect The octet to check
128 | * @return True if the octect is data, false otherwise
129 | */
130 | protected static boolean isData(char octect) {
131 | return (octect < BASELENGTH && base64Alphabet[octect] != -1);
132 | }
133 |
134 | /**
135 | * Check if octect is base64
136 | *
137 | * @param octect The octet to check
138 | * @return True if the octect is base64, false otherwise
139 | */
140 | protected static boolean isBase64(char octect) {
141 | return (isWhiteSpace(octect) || isPad(octect) || isData(octect));
142 | }
143 |
144 | /**
145 | * Encodes hex octects into Base64
146 | *
147 | * @param binaryData Array containing binaryData
148 | * @return Encoded Base64 array
149 | */
150 | public static String encode(byte[] binaryData) {
151 |
152 | if (binaryData == null)
153 | return null;
154 |
155 | int lengthDataBits = binaryData.length*EIGHTBIT;
156 | if (lengthDataBits == 0) {
157 | return "";
158 | }
159 |
160 | int fewerThan24bits = lengthDataBits%TWENTYFOURBITGROUP;
161 | int numberTriplets = lengthDataBits/TWENTYFOURBITGROUP;
162 | int numberQuartet = fewerThan24bits != 0 ? numberTriplets+1 : numberTriplets;
163 | char encodedData[] = null;
164 |
165 | encodedData = new char[numberQuartet*4];
166 |
167 | byte k=0, l=0, b1=0,b2=0,b3=0;
168 |
169 | int encodedIndex = 0;
170 | int dataIndex = 0;
171 | if (fDebug) {
172 | System.out.println("number of triplets = " + numberTriplets );
173 | }
174 |
175 | for (int i=0; i>2) );
211 | }
212 | byte val1 = ((b1 & SIGN)==0)?(byte)(b1>>2):(byte)((b1)>>2^0xc0);
213 | encodedData[encodedIndex++] = lookUpBase64Alphabet[ val1 ];
214 | encodedData[encodedIndex++] = lookUpBase64Alphabet[ k<<4 ];
215 | encodedData[encodedIndex++] = PAD;
216 | encodedData[encodedIndex++] = PAD;
217 | } else if (fewerThan24bits == SIXTEENBIT) {
218 | b1 = binaryData[dataIndex];
219 | b2 = binaryData[dataIndex +1 ];
220 | l = ( byte ) ( b2 &0x0f );
221 | k = ( byte ) ( b1 &0x03 );
222 |
223 | byte val1 = ((b1 & SIGN)==0)?(byte)(b1>>2):(byte)((b1)>>2^0xc0);
224 | byte val2 = ((b2 & SIGN)==0)?(byte)(b2>>4):(byte)((b2)>>4^0xf0);
225 |
226 | encodedData[encodedIndex++] = lookUpBase64Alphabet[ val1 ];
227 | encodedData[encodedIndex++] = lookUpBase64Alphabet[ val2 | ( k<<4 )];
228 | encodedData[encodedIndex++] = lookUpBase64Alphabet[ l<<2 ];
229 | encodedData[encodedIndex++] = PAD;
230 | }
231 |
232 | return new String(encodedData);
233 | }
234 |
235 | /**
236 | * Decodes Base64 data into octects
237 | *
238 | * @param encoded string containing Base64 data
239 | * @return Array containind decoded data.
240 | */
241 | public static byte[] decode(String encoded) {
242 |
243 | if (encoded == null)
244 | return null;
245 |
246 | char[] base64Data = encoded.toCharArray();
247 | // remove white spaces
248 | int len = removeWhiteSpace(base64Data);
249 |
250 | if (len%FOURBYTE != 0) {
251 | return null;//should be divisible by four
252 | }
253 |
254 | int numberQuadruple = (len/FOURBYTE );
255 |
256 | if (numberQuadruple == 0)
257 | return new byte[0];
258 |
259 | byte decodedData[] = null;
260 | byte b1=0,b2=0,b3=0,b4=0;
261 | char d1=0,d2=0,d3=0,d4=0;
262 |
263 | int i = 0;
264 | int encodedIndex = 0;
265 | int dataIndex = 0;
266 | decodedData = new byte[ (numberQuadruple)*3];
267 |
268 | for (; i>4 ) ;
282 | decodedData[encodedIndex++] = (byte)(((b2 & 0xf)<<4 ) |( (b3>>2) & 0xf) );
283 | decodedData[encodedIndex++] = (byte)( b3<<6 | b4 );
284 | }
285 |
286 | if (!isData( (d1 = base64Data[dataIndex++]) ) ||
287 | !isData( (d2 = base64Data[dataIndex++]) )) {
288 | return null;//if found "no data" just return null
289 | }
290 |
291 | b1 = base64Alphabet[d1];
292 | b2 = base64Alphabet[d2];
293 |
294 | d3 = base64Data[dataIndex++];
295 | d4 = base64Data[dataIndex++];
296 | if (!isData( (d3 ) ) ||
297 | !isData( (d4 ) )) {//Check if they are PAD characters
298 | if (isPad( d3 ) && isPad( d4)) { //Two PAD e.g. 3c[Pad][Pad]
299 | if ((b2 & 0xf) != 0)//last 4 bits should be zero
300 | return null;
301 | byte[] tmp = new byte[ i*3 + 1 ];
302 | System.arraycopy( decodedData, 0, tmp, 0, i*3 );
303 | tmp[encodedIndex] = (byte)( b1 <<2 | b2>>4 ) ;
304 | return tmp;
305 | } else if (!isPad( d3) && isPad(d4)) { //One PAD e.g. 3cQ[Pad]
306 | b3 = base64Alphabet[ d3 ];
307 | if ((b3 & 0x3 ) != 0)//last 2 bits should be zero
308 | return null;
309 | byte[] tmp = new byte[ i*3 + 2 ];
310 | System.arraycopy( decodedData, 0, tmp, 0, i*3 );
311 | tmp[encodedIndex++] = (byte)( b1 <<2 | b2>>4 );
312 | tmp[encodedIndex] = (byte)(((b2 & 0xf)<<4 ) |( (b3>>2) & 0xf) );
313 | return tmp;
314 | } else {
315 | return null;//an error like "3c[Pad]r", "3cdX", "3cXd", "3cXX" where X is non data
316 | }
317 | } else { //No PAD e.g 3cQl
318 | b3 = base64Alphabet[ d3 ];
319 | b4 = base64Alphabet[ d4 ];
320 | decodedData[encodedIndex++] = (byte)( b1 <<2 | b2>>4 ) ;
321 | decodedData[encodedIndex++] = (byte)(((b2 & 0xf)<<4 ) |( (b3>>2) & 0xf) );
322 | decodedData[encodedIndex++] = (byte)( b3<<6 | b4 );
323 |
324 | }
325 |
326 | return decodedData;
327 | }
328 |
329 | /**
330 | * remove WhiteSpace from MIME containing encoded Base64 data.
331 | *
332 | * @param data the byte array of base64 data (with WS)
333 | * @return the new length
334 | */
335 | protected static int removeWhiteSpace(char[] data) {
336 | if (data == null)
337 | return 0;
338 |
339 | // count characters that's not whitespace
340 | int newSize = 0;
341 | int len = data.length;
342 | for (int i = 0; i < len; i++) {
343 | if (!isWhiteSpace(data[i]))
344 | data[newSize++] = data[i];
345 | }
346 | return newSize;
347 | }
348 | }
--------------------------------------------------------------------------------
/src/main/java/fr/traqueur/recipes/api/RecipeType.java:
--------------------------------------------------------------------------------
1 | package fr.traqueur.recipes.api;
2 |
3 | import org.bukkit.NamespacedKey;
4 | import org.bukkit.plugin.java.JavaPlugin;
5 |
6 | import java.util.List;
7 |
8 | /**
9 | * Represents a type of recipe.
10 | */
11 | public enum RecipeType {
12 |
13 | /**
14 | * A recipe that is crafted in a crafting table.
15 | */
16 | CRAFTING_SHAPED(9),
17 | /**
18 | * A recipe that is crafted in a crafting table without a specific shape.
19 | */
20 | CRAFTING_SHAPELESS(9),
21 | /**
22 | * A recipe that is crafted in a furnace.
23 | */
24 | BLASTING(1),
25 | /**
26 | * A recipe that is crafted in a campfire.
27 | */
28 | CAMPFIRE_COOKING(1),
29 | /**
30 | * A recipe that is crafted in a smoker.
31 | */
32 | SMOKING(1),
33 | /**
34 | * A recipe that is crafted in a stonecutter.
35 | */
36 | STONE_CUTTING(1),
37 | /**
38 | * A recipe that is crafted in a furnace.
39 | */
40 | SMELTING(1),
41 | /**
42 | * A recipe that is crafted in a smithing table.
43 | */
44 | SMITHING_TRANSFORM(3),
45 | ;
46 |
47 | /**
48 | * The plugin that registered this enum.
49 | */
50 | private static JavaPlugin plugin;
51 |
52 | /**
53 | * The maximum number of ingredients that can be used in this recipe.
54 | */
55 | private final int maxIngredients;
56 |
57 | /**
58 | * Creates a new recipe type.
59 | * @param maxIngredients the maximum number of ingredients that can be used in this recipe
60 | */
61 | RecipeType(int maxIngredients) {
62 | this.maxIngredients = maxIngredients;
63 | }
64 |
65 | /**
66 | * Gets the maximum number of ingredients that can be used in this recipe.
67 | * @return the maximum number of ingredients
68 | */
69 | public int getMaxIngredients() {
70 | return maxIngredients;
71 | }
72 |
73 | /**
74 | * Gets the namespaced key for a recipe with the given key.
75 | * @param key the key
76 | * @return the namespaced key
77 | */
78 | public NamespacedKey getNamespacedKey(String key) {
79 | return new NamespacedKey(plugin, name().toLowerCase() + "_" + key);
80 | }
81 |
82 | /**
83 | * Registers the plugin that is using this enum.
84 | * @param plugin the plugin
85 | */
86 | public static void registerPlugin(JavaPlugin plugin) {
87 | RecipeType.plugin = plugin;
88 | }
89 |
90 | /**
91 | * Gets a list of all the crafting recipes in "smelting" type.
92 | * @return a list of "smeltings" recipes
93 | */
94 | public static List smeltingRecipes() {
95 | return List.of(CAMPFIRE_COOKING, BLASTING, SMOKING, SMELTING);
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/main/java/fr/traqueur/recipes/api/RecipesAPI.java:
--------------------------------------------------------------------------------
1 | package fr.traqueur.recipes.api;
2 |
3 | import fr.traqueur.recipes.api.hook.Hook;
4 | import fr.traqueur.recipes.impl.PrepareCraftListener;
5 | import fr.traqueur.recipes.impl.domains.ItemRecipe;
6 | import fr.traqueur.recipes.impl.domains.recipes.RecipeConfiguration;
7 | import fr.traqueur.recipes.impl.updater.Updater;
8 | import org.bukkit.configuration.file.YamlConfiguration;
9 | import org.bukkit.plugin.java.JavaPlugin;
10 |
11 | import java.io.File;
12 | import java.io.IOException;
13 | import java.net.URL;
14 | import java.nio.file.Files;
15 | import java.nio.file.Path;
16 | import java.security.CodeSource;
17 | import java.util.ArrayList;
18 | import java.util.List;
19 | import java.util.jar.JarEntry;
20 | import java.util.jar.JarInputStream;
21 | import java.util.stream.Stream;
22 |
23 | /**
24 | * RecipesAPI is the main class of the API
25 | * It allows you to create and manage recipes
26 | */
27 | public final class RecipesAPI {
28 |
29 | /**
30 | * The plugin instance
31 | */
32 | private final JavaPlugin plugin;
33 |
34 | /**
35 | * If the debug mode is enabled
36 | */
37 | private final boolean debug;
38 |
39 | /**
40 | * The list of recipes
41 | */
42 | private final List recipes;
43 |
44 | /**
45 | * Create a new instance of RecipesAPI with yml support enabled
46 | * @param plugin The plugin instance
47 | * @param debug If the debug mode is enabled
48 | */
49 | public RecipesAPI(JavaPlugin plugin, boolean debug) {
50 | this(plugin, debug, true);
51 | }
52 |
53 | /**
54 | * Create a new instance of RecipesAPI
55 | * @param plugin The plugin instance
56 | * @param debug If the debug mode is enabled
57 | * @param enableYmlSupport If the yml support is enabled
58 | */
59 | public RecipesAPI(JavaPlugin plugin, boolean debug, boolean enableYmlSupport) {
60 | this.debug = debug;
61 | this.plugin = plugin;
62 | this.recipes = new ArrayList<>();
63 |
64 | RecipeType.registerPlugin(plugin);
65 |
66 | plugin.getServer().getPluginManager().registerEvents(new PrepareCraftListener(this), plugin);
67 |
68 | if(enableYmlSupport) {
69 | var recipeFolder = new File(plugin.getDataFolder(), "recipes/");
70 | if (!recipeFolder.exists() && !recipeFolder.mkdirs()) {
71 | plugin.getLogger().warning("Could not create recipes folder.");
72 | return;
73 | }
74 | this.loadDefaultRecipes();
75 | this.addConfiguredRecipes(recipeFolder);
76 | }
77 |
78 | if(this.debug) {
79 | Hook.HOOKS.stream()
80 | .filter(hook -> hook.isEnable(plugin))
81 | .forEach(hook -> this.plugin.getLogger().info("Hook enabled: " + hook.getPluginName()));
82 |
83 | Updater.update("RecipesAPI");
84 | }
85 | }
86 |
87 | /**
88 | * Load the default recipes from the jar
89 | */
90 | private void loadDefaultRecipes() {
91 | try {
92 | CodeSource src = getClass().getProtectionDomain().getCodeSource();
93 | if (src != null) {
94 | URL jar = src.getLocation();
95 | try (JarInputStream jarStream = new JarInputStream(jar.openStream())) {
96 | JarEntry entry;
97 | while ((entry = jarStream.getNextJarEntry()) != null) {
98 | if (entry.getName().startsWith("recipes/") && entry.getName().endsWith(".yml")) {
99 | File outFile = new File(plugin.getDataFolder(), entry.getName());
100 | if (!outFile.exists()) {
101 | plugin.saveResource(entry.getName(), false);
102 | }
103 | }
104 | }
105 | }
106 | }
107 | } catch (IOException e) {
108 | plugin.getLogger().warning("Could not load default recipes.");
109 | plugin.getServer().getPluginManager().disablePlugin(plugin);
110 | }
111 | }
112 |
113 | /**
114 | * Add all the recipes in the recipe folder to the list of recipes
115 | * @param recipeFolder The folder containing the recipes
116 | */
117 | private void addConfiguredRecipes(File recipeFolder) {
118 |
119 | try (Stream stream = Files.walk(recipeFolder.toPath())) {
120 | stream.skip(1)
121 | .map(Path::toFile)
122 | .filter(File::isFile)
123 | .filter(e -> e.getName().endsWith(".yml"))
124 | .forEach(this::loadRecipe);
125 | } catch (IOException exception) {
126 | plugin.getLogger().warning("Could not load recipes.");
127 | plugin.getServer().getPluginManager().disablePlugin(plugin);
128 | }
129 | }
130 |
131 | /**
132 | * Load a recipe from a file
133 | * @param file The file to load the recipe from
134 | */
135 | private void loadRecipe(File file) {
136 | YamlConfiguration configuration = YamlConfiguration.loadConfiguration(file);
137 | var recipe = new RecipeConfiguration(this.plugin, file.getName().replace(".yml", ""), configuration)
138 | .build();
139 | this.addRecipe(recipe);
140 | }
141 |
142 | /**
143 | * Unregister all the recipes in the list of recipes from the server
144 | */
145 | public void unregisterRecipes() {
146 | for (ItemRecipe recipe : recipes) {
147 | plugin.getServer().removeRecipe(recipe.getKey());
148 | }
149 | recipes.clear();
150 | }
151 |
152 | /**
153 | * Add a recipe to the list of recipes
154 | * @param recipe The recipe to add
155 | */
156 | public void addRecipe(ItemRecipe recipe) {
157 | if (recipes.stream().anyMatch(r -> r.getKey().equals(recipe.getKey()))) {
158 | throw new IllegalArgumentException("Recipe already registered");
159 | }
160 | this.recipes.add(recipe);
161 | if(plugin.getServer().getRecipe(recipe.getKey()) == null) {
162 | plugin.getServer().addRecipe(recipe.toBukkitRecipe());
163 | }
164 | if(this.debug) {
165 | plugin.getLogger().info("Registering recipe: " + recipe.getKey());
166 | }
167 | }
168 |
169 | /**
170 | * Remove a recipe from the list of recipes
171 | * @param recipe The recipe to remove
172 | */
173 | public void removeRecipe(ItemRecipe recipe) {
174 | plugin.getServer().removeRecipe(recipe.getKey());
175 | this.recipes.remove(recipe);
176 | if(this.debug) {
177 | plugin.getLogger().info("Unregistering recipe: " + recipe.getKey());
178 | }
179 | }
180 |
181 | /**
182 | * Get the list of recipes
183 | * @return The list of recipes
184 | */
185 | public List getRecipes() {
186 | return recipes;
187 | }
188 |
189 | /**
190 | * Get the plugin instance
191 | * @return The plugin instance
192 | */
193 | public JavaPlugin getPlugin() {
194 | return plugin;
195 | }
196 |
197 | /**
198 | * Get if the debug mode is enabled
199 | * @return If the debug mode is enabled
200 | */
201 | public boolean isDebug() {
202 | return debug;
203 | }
204 | }
--------------------------------------------------------------------------------
/src/main/java/fr/traqueur/recipes/api/TagRegistry.java:
--------------------------------------------------------------------------------
1 | package fr.traqueur.recipes.api;
2 |
3 | import org.bukkit.Material;
4 | import org.bukkit.Tag;
5 |
6 | import java.lang.reflect.Field;
7 | import java.util.HashMap;
8 | import java.util.Map;
9 | import java.util.Optional;
10 |
11 | /**
12 | * This class is used to register tags.
13 | */
14 | public class TagRegistry {
15 |
16 | /**
17 | * The map of tags.
18 | */
19 | private static final Map> tagMap = new HashMap<>();
20 |
21 | static {
22 | for (Field field : Tag.class.getDeclaredFields()) {
23 | if (Tag.class.isAssignableFrom(field.getType())) {
24 | try {
25 | Class> genericType = (Class>) ((java.lang.reflect.ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
26 | if (Material.class.isAssignableFrom(genericType)) {
27 | register(field.getName(), (Tag) field.get(null));
28 | }
29 | } catch (Exception exception) {
30 | throw new RuntimeException("Failed to register tag: " + field.getName(), exception);
31 | }
32 | }
33 | }
34 | }
35 |
36 | /**
37 | * Register a tag.
38 | * @param key the key of the tag.
39 | * @param tag the tag.
40 | */
41 | private static void register(String key, Tag tag) {
42 | tagMap.put(key, tag);
43 | }
44 |
45 | /**
46 | * Get a tag by its key.
47 | * @param key the key of the tag.
48 | * @return the tag.
49 | */
50 | public static Optional> getTag(String key) {
51 | return Optional.ofNullable(tagMap.get(key.toUpperCase()));
52 | }
53 | }
--------------------------------------------------------------------------------
/src/main/java/fr/traqueur/recipes/api/domains/Ingredient.java:
--------------------------------------------------------------------------------
1 | package fr.traqueur.recipes.api.domains;
2 |
3 | import org.bukkit.inventory.ItemStack;
4 | import org.bukkit.inventory.RecipeChoice;
5 |
6 | /**
7 | * Base class for ingredients.
8 | */
9 | public abstract class Ingredient {
10 |
11 | /**
12 | * The sign of the ingredient.
13 | */
14 | private final Character sign;
15 |
16 | /**
17 | * Constructor.
18 | * @param sign The sign of the ingredient.
19 | */
20 | public Ingredient(Character sign) {
21 | this.sign = sign;
22 | }
23 |
24 | /**
25 | * Get the sign of the ingredient.
26 | * @return The sign of the ingredient.
27 | */
28 | public Character sign() {
29 | return this.sign;
30 | }
31 |
32 |
33 | /**
34 | * Check if the item is similar to the ingredient.
35 | * @param item The item to check.
36 | * @return true if the item is similar to the ingredient, false otherwise.
37 | */
38 | public abstract boolean isSimilar(ItemStack item);
39 |
40 | /**
41 | * Get the choice of the ingredient.
42 | * @return The choice of the ingredient.
43 | */
44 | public abstract RecipeChoice choice();
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/fr/traqueur/recipes/api/domains/Recipe.java:
--------------------------------------------------------------------------------
1 | package fr.traqueur.recipes.api.domains;
2 |
3 | import fr.traqueur.recipes.api.RecipeType;
4 | import fr.traqueur.recipes.impl.domains.ItemRecipe;
5 | import fr.traqueur.recipes.impl.domains.ingredients.ItemStackIngredient;
6 | import fr.traqueur.recipes.impl.domains.ingredients.MaterialIngredient;
7 | import fr.traqueur.recipes.impl.domains.ingredients.StrictItemStackIngredient;
8 | import fr.traqueur.recipes.impl.domains.ingredients.TagIngredient;
9 | import org.bukkit.Material;
10 | import org.bukkit.Tag;
11 | import org.bukkit.inventory.ItemStack;
12 |
13 | import java.util.List;
14 |
15 | /**
16 | * Represents a recipe.
17 | */
18 | public interface Recipe {
19 |
20 | /**
21 | * Set the name of the recipe.
22 | * @param name The name of the recipe.
23 | * @return The name of the recipe.
24 | */
25 | Recipe setName(String name);
26 |
27 | /**
28 | * Set the result of the recipe.
29 | * @param result The result of the recipe.
30 | * @return The result of the recipe.
31 | */
32 | Recipe setResult(ItemStack result);
33 |
34 | /**
35 | * Set the amount of the result.
36 | * @param amount The amount of the result.
37 | * @return The amount of the result.
38 | */
39 | Recipe setAmount(int amount);
40 |
41 | /**
42 | * Set the type of the recipe.
43 | * @param type The type of the recipe.
44 | * @return The type of the recipe.
45 | */
46 | Recipe setType(RecipeType type);
47 |
48 |
49 | /**
50 | * Add an ingredient to the recipe.
51 | * @param ingredients The ingredients to add.
52 | * @return The recipe.
53 | */
54 | default Recipe addIngredient(Ingredient... ingredients) {
55 | for (Ingredient ingredient : ingredients) {
56 | addIngredient(ingredient);
57 | }
58 | return this;
59 | }
60 |
61 | /**
62 | * Add an ingredient to the recipe.
63 | * @param tag The tag of the ingredient.
64 | * @return The recipe.
65 | */
66 | default Recipe addIngredient(Tag tag) {
67 | return addIngredient(tag, null);
68 | }
69 |
70 | /**
71 | * Add an ingredient to the recipe.
72 | * @param tag The tag of the ingredient.
73 | * @param sign The sign of the ingredient.
74 | * @return The recipe.
75 | */
76 | default Recipe addIngredient(Tag tag, Character sign) {
77 | return addIngredient(new TagIngredient(tag, sign));
78 | }
79 |
80 | /**
81 | * Add an ingredient to the recipe.
82 | * @param item The item of the ingredient.
83 | * @return The recipe.
84 | */
85 | default Recipe addIngredient(ItemStack item) {
86 | if(this.getType() == RecipeType.CRAFTING_SHAPED) {
87 | throw new UnsupportedOperationException("You can't add an ingredient withou sign to a shaped recipe");
88 | }
89 | return addIngredient(item, null);
90 | }
91 |
92 | /**
93 | * Add an ingredient to the recipe.
94 | * @param item The item of the ingredient.
95 | * @param strict If the ingredient is strict.
96 | * @return The recipe.
97 | */
98 | default Recipe addIngredient(ItemStack item, boolean strict) {
99 | return addIngredient(item, null, strict);
100 | }
101 |
102 | /**
103 | * Add an ingredient to the recipe.
104 | * @param item The item of the ingredient.
105 | * @param sign The sign of the ingredient.
106 | * @return The recipe.
107 | */
108 | default Recipe addIngredient(ItemStack item, Character sign) {
109 | return addIngredient(item, sign, false);
110 | }
111 |
112 | /**
113 | * Add an ingredient to the recipe.
114 | * @param item The item of the ingredient.
115 | * @param sign The sign of the ingredient.
116 | * @param strict If the ingredient is strict.
117 | * @return The recipe.
118 | */
119 | default Recipe addIngredient(ItemStack item, Character sign, boolean strict) {
120 | return addIngredient(strict ? new StrictItemStackIngredient(item, sign)
121 | : new ItemStackIngredient(item, sign));
122 | }
123 |
124 | /**
125 | * Add an ingredient to the recipe.
126 | * @param material The material of the ingredient.
127 | * @return The recipe.
128 | */
129 | default Recipe addIngredient(Material material) {
130 | if(this.getType() == RecipeType.CRAFTING_SHAPED) {
131 | throw new UnsupportedOperationException("You can't add an ingredient without sign to a shaped recipe");
132 | }
133 | return addIngredient(material, null);
134 | }
135 |
136 | /**
137 | * Add an ingredient to the recipe.
138 | * @param material The material of the ingredient.
139 | * @param sign The sign of the ingredient.
140 | * @return The recipe.
141 | */
142 | default Recipe addIngredient(Material material, Character sign) {
143 | return addIngredient(new MaterialIngredient(material, sign));
144 | }
145 |
146 | /**
147 | * Add an ingredient to the recipe.
148 | * @param ingredient The ingredient to add.
149 | * @return The recipe.
150 | */
151 | Recipe addIngredient(Ingredient ingredient);
152 |
153 | /**
154 | * Set the group of the recipe.
155 | * @param group The group of the recipe.
156 | * @return The group of the recipe.
157 | */
158 | Recipe setGroup(String group);
159 |
160 | /**
161 | * Set the category of the recipe.
162 | * @param category The category of the recipe.
163 | * @return The category of the recipe.
164 | */
165 | Recipe setCategory(String category);
166 |
167 | /**
168 | * Set the pattern of the recipe.
169 | * @param pattern The pattern of the recipe.
170 | * @return The pattern of the recipe.
171 | */
172 | Recipe setPattern(String... pattern);
173 |
174 | /**
175 | * Set the cooking time of the recipe.
176 | * @return The cooking time of the recipe.
177 | * @param cookingTime The cooking time of the recipe.
178 | */
179 | Recipe setCookingTime(int cookingTime);
180 |
181 | /**
182 | * Set the experience of the recipe.
183 | * @return The experience of the recipe.
184 | * @param experience The experience of the recipe.
185 | */
186 | Recipe setExperience(float experience);
187 |
188 | /**
189 | * Get the type of the recipe.
190 | * @return The type of the recipe.
191 | */
192 | RecipeType getType();
193 |
194 | /**
195 | * Build the recipe.
196 | * @return The recipe.
197 | */
198 | ItemRecipe build();
199 |
200 | /**
201 | * Create a new item recipe.
202 | * @param ingredientList The list of ingredients.
203 | * @param type The type of the recipe.
204 | * @param pattern The pattern of the recipe.
205 | * @param cookingTime The cooking time of the recipe.
206 | * @param name The name of the recipe.
207 | * @param group The group of the recipe.
208 | * @param category The category of the recipe.
209 | * @param result The result of the recipe.
210 | * @param amount The amount of the result.
211 | * @param experience The experience of the recipe.
212 | * @return The item recipe.
213 | */
214 | default ItemRecipe getItemRecipe(List ingredientList, RecipeType type, String[] pattern, int cookingTime, String name, String group, String category, ItemStack result, int amount, float experience) {
215 | if (ingredientList.isEmpty()) {
216 | throw new IllegalArgumentException("Ingredients are not set");
217 | }
218 |
219 | if (type == RecipeType.CRAFTING_SHAPED && pattern == null) {
220 | throw new IllegalArgumentException("Pattern is not set");
221 | }
222 |
223 | if (type == RecipeType.CRAFTING_SHAPED && pattern.length == 0) {
224 | throw new IllegalArgumentException("Pattern is empty");
225 | }
226 |
227 | if(RecipeType.smeltingRecipes().contains(type) && cookingTime == 0) {
228 | throw new IllegalArgumentException("Cooking time is not set");
229 | }
230 |
231 | return new ItemRecipe(name, group, category, type, result, amount, ingredientList.toArray(new Ingredient[0]), pattern, cookingTime, experience);
232 | }
233 |
234 | }
235 |
--------------------------------------------------------------------------------
/src/main/java/fr/traqueur/recipes/api/hook/Hook.java:
--------------------------------------------------------------------------------
1 | package fr.traqueur.recipes.api.hook;
2 |
3 | import fr.traqueur.recipes.api.domains.Ingredient;
4 | import fr.traqueur.recipes.impl.hook.Hooks;
5 | import org.bukkit.inventory.ItemStack;
6 | import org.bukkit.plugin.java.JavaPlugin;
7 |
8 | import java.util.ArrayList;
9 | import java.util.List;
10 |
11 | /**
12 | * Hooks are used to create custom ingredients
13 | */
14 | public interface Hook {
15 |
16 | /**
17 | * The list of hooks
18 | */
19 | List HOOKS = new ArrayList<>(List.of(Hooks.values()));
20 |
21 | /**
22 | * Add a new hook
23 | * @param hook The hook to add
24 | */
25 | static void addHook(Hook hook) {
26 | HOOKS.add(hook);
27 | }
28 |
29 | /**
30 | * Get a plugin name of the hook
31 | * @return The hook's plugin name
32 | */
33 | String getPluginName();
34 |
35 | /**
36 | * Get an ingredient from the hook
37 | * @param data The data of the ingredient
38 | * @param sign The sign of the ingredient
39 | * @return The ingredient
40 | */
41 | Ingredient getIngredient(String data, Character sign);
42 |
43 | /**
44 | * Check if the plugin is enabled
45 | * @param plugin The plugin which use the API
46 | * @return If the plugin is enabled
47 | */
48 | default boolean isEnable(JavaPlugin plugin) {
49 | return plugin.getServer().getPluginManager().getPlugin(getPluginName()) != null;
50 | }
51 |
52 | ItemStack getItemStack(String resultPart);
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/fr/traqueur/recipes/impl/PrepareCraftListener.java:
--------------------------------------------------------------------------------
1 | package fr.traqueur.recipes.impl;
2 |
3 | import fr.traqueur.recipes.api.RecipeType;
4 | import fr.traqueur.recipes.api.RecipesAPI;
5 | import fr.traqueur.recipes.api.domains.Ingredient;
6 | import fr.traqueur.recipes.impl.domains.ItemRecipe;
7 | import org.bukkit.Material;
8 | import org.bukkit.event.EventHandler;
9 | import org.bukkit.event.Listener;
10 | import org.bukkit.event.block.BlockCookEvent;
11 | import org.bukkit.event.inventory.PrepareItemCraftEvent;
12 | import org.bukkit.event.inventory.PrepareSmithingEvent;
13 | import org.bukkit.inventory.*;
14 |
15 | import java.util.ArrayList;
16 | import java.util.Arrays;
17 | import java.util.List;
18 | import java.util.Objects;
19 | import java.util.concurrent.atomic.AtomicBoolean;
20 |
21 | /**
22 | * This class is used to listen to events that are related to the api.
23 | */
24 | public class PrepareCraftListener implements Listener {
25 |
26 | /**
27 | * The API instance.
28 | */
29 | private final RecipesAPI api;
30 |
31 | /**
32 | * Creates a new PrepareCraftListener instance.
33 | * @param api the API instance
34 | */
35 | public PrepareCraftListener(RecipesAPI api) {
36 | this.api = api;
37 | }
38 |
39 | /**
40 | * Get the recipes for an item.
41 | * @param item the item
42 | * @param recipeType the recipe type
43 | */
44 | private List getRecipeFor(ItemStack item, Class recipeType) {
45 | List recipes = new ArrayList<>();
46 | for (Recipe recipe : api.getPlugin().getServer().getRecipesFor(item)) {
47 | if (recipeType.isInstance(recipe)) {
48 | recipes.add(recipeType.cast(recipe));
49 | }
50 | }
51 | return recipes;
52 | }
53 |
54 |
55 | /**
56 | * This method is called when a block is smelted.
57 | * @param event the event
58 | */
59 | @EventHandler
60 | public void onSmelt(BlockCookEvent event) {
61 | if(event.isCancelled()) {
62 | return;
63 | }
64 | ItemStack item = event.getSource();
65 | if (item == null || item.getType() == Material.AIR) return;
66 | ItemStack result = event.getResult();
67 | var recipes = getRecipeFor(result, FurnaceRecipe.class);
68 |
69 | List itemRecipes = api.getRecipes().stream()
70 | .filter(itemRecipe -> RecipeType.smeltingRecipes().contains(itemRecipe.recipeType()))
71 | .toList();
72 |
73 | for (ItemRecipe itemRecipe : itemRecipes) {
74 | recipes.stream()
75 | .filter(recipe -> recipe.getKey().equals(itemRecipe.getKey()))
76 | .findFirst()
77 | .ifPresent(recipe -> {
78 | if(!isSimilar(item, itemRecipe.ingredients()[0])) {
79 | event.setCancelled(true);
80 | }
81 | });
82 | }
83 | }
84 |
85 | /**
86 | * This method is called when a smithing transformation is prepared.
87 | * @param event the event
88 | */
89 | @EventHandler
90 | public void onSmithingTransform(PrepareSmithingEvent event) {
91 | if(event.getInventory().getRecipe() == null) {
92 | return;
93 | }
94 | SmithingRecipe recipe = (SmithingRecipe) event.getInventory().getRecipe();
95 |
96 | ItemStack item = event.getResult();
97 | if (item == null || item.getType() == Material.AIR) return;
98 |
99 | ItemStack template = event.getInventory().getItem(0);
100 | ItemStack base = event.getInventory().getItem(1);
101 | ItemStack addition = event.getInventory().getItem(2);
102 |
103 | if(recipe instanceof SmithingTrimRecipe) {
104 | return;
105 | }
106 |
107 |
108 | List itemRecipes = api.getRecipes().stream()
109 | .filter(itemRecipe -> itemRecipe.recipeType() == RecipeType.SMITHING_TRANSFORM)
110 | .toList();
111 | for (ItemRecipe itemRecipe : itemRecipes) {
112 | if (!itemRecipe.getKey()
113 | .equals(recipe.getKey()))
114 | continue;
115 | Ingredient templateIngredient = itemRecipe.ingredients()[0];
116 | Ingredient baseIngredient = itemRecipe.ingredients()[1];
117 | Ingredient additionIngredient = itemRecipe.ingredients()[2];
118 |
119 | boolean isSimilar = isSimilar(template, templateIngredient)
120 | && isSimilar(base, baseIngredient)
121 | && isSimilar(addition, additionIngredient);
122 |
123 | if(!isSimilar) {
124 | event.setResult(new ItemStack(Material.AIR));
125 | return;
126 | }
127 | }
128 | }
129 |
130 | /**
131 | * Check if an item is similar to an ingredient.
132 | * @param item the item
133 | * @param itemIngredient the ingredient
134 | * @return true if the item is similar to the ingredient, false otherwise
135 | */
136 | private boolean isSimilar(ItemStack item, Ingredient itemIngredient) {
137 | return itemIngredient.isSimilar(item);
138 | }
139 |
140 | /**
141 | * This method is called when an item is prepared to be crafted.
142 | * @param event the event
143 | */
144 | @EventHandler
145 | public void onPrepareCraft(PrepareItemCraftEvent event) {
146 | Recipe recipe = event.getRecipe();
147 | if (recipe == null) return;
148 |
149 | var itemRecipes = api.getRecipes().stream()
150 | .filter(itemRecipe -> itemRecipe.recipeType() == RecipeType.CRAFTING_SHAPED || itemRecipe.recipeType() == RecipeType.CRAFTING_SHAPELESS)
151 | .toList();
152 |
153 | for (ItemRecipe itemRecipe : itemRecipes) {
154 | if(recipe instanceof ShapedRecipe shapedRecipe && itemRecipe.recipeType() == RecipeType.CRAFTING_SHAPED) {
155 | if (!shapedRecipe.getKey().equals(itemRecipe.getKey())) continue;
156 | this.checkGoodShapedRecipe(itemRecipe, event);
157 | }
158 |
159 | if(recipe instanceof ShapelessRecipe shapelessRecipe && itemRecipe.recipeType() == RecipeType.CRAFTING_SHAPELESS) {
160 | if(!shapelessRecipe.getKey().equals(itemRecipe.getKey())) continue;
161 | this.checkGoodShapelessRecipe(itemRecipe, event);
162 | }
163 | }
164 | }
165 |
166 | /**
167 | * Check if the recipe is good for a shaped recipe.
168 | * @param itemRecipe the item recipe
169 | * @param event the event
170 | */
171 | private void checkGoodShapedRecipe(ItemRecipe itemRecipe, PrepareItemCraftEvent event) {
172 | AtomicBoolean isSimilar = new AtomicBoolean(true);
173 | ItemStack[] matrix = event.getInventory().getMatrix();
174 | matrix = Arrays.stream(matrix).filter(stack -> stack != null && stack.getType() != Material.AIR).toArray(ItemStack[]::new);
175 | String[] pattern = Arrays.stream(itemRecipe.pattern()).map(s -> s.split("")).flatMap(Arrays::stream).toArray(String[]::new);
176 |
177 | for (int i = 0; i < matrix.length; i++) {
178 | ItemStack stack = matrix[i];
179 | char sign = pattern[i].charAt(0);
180 | Arrays.stream(itemRecipe.ingredients()).filter(ingredient -> ingredient.sign() == sign).findFirst().ifPresent(ingredient -> {
181 | isSimilar.set(ingredient.isSimilar(stack));
182 | });
183 | if(!isSimilar.get()) {
184 | event.getInventory().setResult(new ItemStack(Material.AIR));
185 | return;
186 | }
187 | }
188 | }
189 |
190 | /**
191 | * Check if the recipe is good for a shapeless recipe.
192 | * @param itemRecipe the item recipe
193 | * @param event the event
194 | */
195 | private void checkGoodShapelessRecipe(ItemRecipe itemRecipe, PrepareItemCraftEvent event) {
196 | List matrix = Arrays.stream(event.getInventory().getMatrix()).filter(Objects::nonNull).filter(it -> it.getType() != Material.AIR).toList();
197 | Ingredient[] itemIngredients = itemRecipe.ingredients();
198 |
199 | AtomicBoolean isSimilar = new AtomicBoolean(true);
200 | for (Ingredient ingredient : itemIngredients) {
201 | boolean found = matrix.stream().anyMatch(stack -> {
202 | if (stack == null || stack.getType() == Material.AIR) return false;
203 | return ingredient.isSimilar(stack);
204 | });
205 | if (!found) {
206 | isSimilar.set(false);
207 | break;
208 | }
209 | }
210 |
211 | if (!isSimilar.get()) {
212 | event.getInventory().setResult(new ItemStack(Material.AIR));
213 | }
214 | }
215 | }
216 |
--------------------------------------------------------------------------------
/src/main/java/fr/traqueur/recipes/impl/domains/ItemRecipe.java:
--------------------------------------------------------------------------------
1 | package fr.traqueur.recipes.impl.domains;
2 |
3 | import fr.traqueur.recipes.api.RecipeType;
4 | import fr.traqueur.recipes.api.domains.Ingredient;
5 | import org.bukkit.NamespacedKey;
6 | import org.bukkit.inventory.*;
7 | import org.bukkit.inventory.recipe.CookingBookCategory;
8 | import org.bukkit.inventory.recipe.CraftingBookCategory;
9 |
10 | /**
11 | * This class represents a recipe for an item
12 | * @param recipeName The name of the recipe
13 | * @param group The group of the recipe
14 | * @param category The category of the recipe
15 | * @param recipeType The type of the recipe
16 | * @param result The result of the recipe
17 | * @param amount The amount of the result
18 | * @param ingredients The ingredients of the recipe
19 | * @param pattern The pattern of the recipe
20 | * @param cookingTime The cooking time of the recipe
21 | * @param experience The experience of the recipe
22 | */
23 | public record ItemRecipe(String recipeName, String group, String category, RecipeType recipeType, ItemStack result, int amount, Ingredient[] ingredients,
24 | String[] pattern, int cookingTime, float experience) {
25 |
26 | /**
27 | * Convert the recipe to a bukkit recipe
28 | * @param key The key of the recipe
29 | * @param result The result of the recipe
30 | * @return The bukkit recipe
31 | */
32 | public Recipe toBukkitRecipe(NamespacedKey key, ItemStack result) {
33 | return switch (this.recipeType) {
34 | case CRAFTING_SHAPED -> {
35 | var shapedRecipe = new ShapedRecipe(key, result);
36 | shapedRecipe.shape(pattern);
37 | for (Ingredient ingredient : ingredients) {
38 | shapedRecipe.setIngredient(ingredient.sign(), ingredient.choice());
39 | }
40 | if (!group.isEmpty()) {
41 | shapedRecipe.setGroup(group);
42 | }
43 | if (!category.isEmpty()) {
44 | shapedRecipe.setCategory(CraftingBookCategory.valueOf(category.toUpperCase()));
45 | }
46 | yield shapedRecipe;
47 | }
48 | case CRAFTING_SHAPELESS -> {
49 | var shapelessRecipe = new ShapelessRecipe(key, result);
50 | for (Ingredient ingredient : ingredients) {
51 | shapelessRecipe.addIngredient(ingredient.choice());
52 | }
53 | if (!group.isEmpty()) {
54 | shapelessRecipe.setGroup(group);
55 | }
56 | if (!category.isEmpty()) {
57 | shapelessRecipe.setCategory(CraftingBookCategory.valueOf(category.toUpperCase()));
58 | }
59 | yield shapelessRecipe;
60 | }
61 | case BLASTING -> {
62 | var blastingRecipe = new BlastingRecipe(key, result, ingredients[0].choice(), experience, cookingTime);
63 | if (!group.isEmpty()) {
64 | blastingRecipe.setGroup(group);
65 | }
66 | if (!category.isEmpty()) {
67 | blastingRecipe.setCategory(CookingBookCategory.valueOf(category.toUpperCase()));
68 | }
69 | yield blastingRecipe;
70 | }
71 | case CAMPFIRE_COOKING -> {
72 | var campfireRecipe = new CampfireRecipe(key, result, ingredients[0].choice(), experience, cookingTime);
73 | if (!group.isEmpty()) {
74 | campfireRecipe.setGroup(group);
75 | }
76 | if (!category.isEmpty()) {
77 | campfireRecipe.setCategory(CookingBookCategory.valueOf(category.toUpperCase()));
78 | }
79 | yield campfireRecipe;
80 | }
81 | case SMOKING -> {
82 | var smokingRecipe = new SmokingRecipe(key, result, ingredients[0].choice(), experience, cookingTime);
83 | if (!group.isEmpty()) {
84 | smokingRecipe.setGroup(group);
85 | }
86 | if (!category.isEmpty()) {
87 | smokingRecipe.setCategory(CookingBookCategory.valueOf(category.toUpperCase()));
88 | }
89 | yield smokingRecipe;
90 | }
91 | case STONE_CUTTING -> {
92 | var stonecuttingRecipe = new StonecuttingRecipe(key, result, ingredients[0].choice());
93 | if (!group.isEmpty()) {
94 | stonecuttingRecipe.setGroup(group);
95 | }
96 | yield stonecuttingRecipe;
97 | }
98 | case SMELTING -> {
99 | var furnaceRecipe = new FurnaceRecipe(key, result, ingredients[0].choice(), experience, cookingTime);
100 | if (!group.isEmpty()) {
101 | furnaceRecipe.setGroup(group);
102 | }
103 | if (!category.isEmpty()) {
104 | furnaceRecipe.setCategory(CookingBookCategory.valueOf(category.toUpperCase()));
105 | }
106 | yield furnaceRecipe;
107 | }
108 | case SMITHING_TRANSFORM -> new SmithingTransformRecipe(key, result, ingredients[0].choice(), ingredients[1].choice(), ingredients[2].choice());
109 | };
110 | }
111 |
112 | /**
113 | * Convert the recipe to a bukkit recipe
114 | * @return The bukkit recipe
115 | */
116 | public Recipe toBukkitRecipe() {
117 | ItemStack result = new ItemStack(this.result());
118 | result.setAmount(this.amount());
119 | NamespacedKey key = this.getKey();
120 | return this.toBukkitRecipe(key, result);
121 | }
122 |
123 | /**
124 | * Get the key of the recipe
125 | * @return The key of the recipe
126 | */
127 | public NamespacedKey getKey() {
128 | return this.recipeType().getNamespacedKey(recipeName);
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/src/main/java/fr/traqueur/recipes/impl/domains/ingredients/ItemStackIngredient.java:
--------------------------------------------------------------------------------
1 | package fr.traqueur.recipes.impl.domains.ingredients;
2 |
3 | import fr.traqueur.recipes.api.domains.Ingredient;
4 | import org.bukkit.NamespacedKey;
5 | import org.bukkit.inventory.ItemStack;
6 | import org.bukkit.inventory.RecipeChoice;
7 | import org.bukkit.inventory.meta.ItemMeta;
8 |
9 | import java.util.Objects;
10 |
11 | /**
12 | * This class represents an ingredient that is an item stack
13 | */
14 | public class ItemStackIngredient extends Ingredient {
15 |
16 | /**
17 | * The item of the ingredient
18 | */
19 | protected final ItemStack item;
20 |
21 | /**
22 | * Create a new ItemStackIngredient
23 | * @param item The item of the ingredient
24 | * @param sign The sign of the ingredient
25 | */
26 | public ItemStackIngredient(ItemStack item, Character sign) {
27 | super(sign);
28 | this.item = item;
29 | }
30 |
31 | /**
32 | * Create a new ItemStackIngredient
33 | * @param item The item of the ingredient
34 | */
35 | public ItemStackIngredient(ItemStack item) {
36 | this(item, null);
37 | }
38 |
39 | /**
40 | * {@inheritDoc}
41 | */
42 | @Override
43 | public boolean isSimilar(ItemStack item) {
44 |
45 | return item.getType() == this.item.getType()
46 | && item.getAmount() >= this.item.getAmount()
47 | && item.hasItemMeta() == this.item.hasItemMeta()
48 | && (!item.hasItemMeta() || similarMeta(item.getItemMeta(), this.item.getItemMeta()));
49 | }
50 |
51 | /**
52 | * Check if the meta of the two items are similar
53 | * @param sourceMeta The source meta
54 | * @param ingredientMeta The ingredient meta
55 | * @return True if the meta of the two items are similar
56 | */
57 | private boolean similarMeta(ItemMeta sourceMeta, ItemMeta ingredientMeta) {
58 | for (NamespacedKey key : sourceMeta.getPersistentDataContainer().getKeys()) {
59 | if (!ingredientMeta.getPersistentDataContainer().has(key)) {
60 | System.out.println("Key " + key + " not found in ingredient meta");
61 | return false;
62 | }
63 | }
64 |
65 | boolean lore = sourceMeta.hasLore() == ingredientMeta.hasLore() && (!sourceMeta.hasLore()
66 | || Objects.equals(sourceMeta.getLore(), ingredientMeta.getLore()));
67 |
68 | boolean customData = sourceMeta.hasCustomModelData() == ingredientMeta.hasCustomModelData()
69 | && (!sourceMeta.hasCustomModelData()
70 | || sourceMeta.getCustomModelData() == ingredientMeta.getCustomModelData());
71 |
72 | return lore && customData;
73 | }
74 |
75 | /**
76 | * {@inheritDoc}
77 | */
78 | @Override
79 | public RecipeChoice choice() {
80 | return new RecipeChoice.MaterialChoice(this.item.getType());
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/main/java/fr/traqueur/recipes/impl/domains/ingredients/MaterialIngredient.java:
--------------------------------------------------------------------------------
1 | package fr.traqueur.recipes.impl.domains.ingredients;
2 |
3 | import fr.traqueur.recipes.api.domains.Ingredient;
4 | import org.bukkit.Material;
5 | import org.bukkit.inventory.ItemStack;
6 | import org.bukkit.inventory.RecipeChoice;
7 |
8 | /**
9 | * A material ingredient
10 | */
11 | public class MaterialIngredient extends Ingredient {
12 |
13 | /**
14 | * The material of the ingredient
15 | */
16 | private final Material material;
17 |
18 | /**
19 | * Create a new MaterialIngredient
20 | * @param material The material of the ingredient
21 | * @param sign The sign of the ingredient
22 | */
23 | public MaterialIngredient(Material material, Character sign) {
24 | super(sign);
25 | this.material = material;
26 | }
27 |
28 | /**
29 | * Create a new MaterialIngredient
30 | * @param material The material of the ingredient
31 | */
32 | public MaterialIngredient(Material material) {
33 | this(material, null);
34 | }
35 |
36 | /**
37 | * {@inheritDoc}
38 | */
39 | @Override
40 | public boolean isSimilar(ItemStack item) {
41 | return item.getType() == this.material;
42 | }
43 |
44 | /**
45 | * {@inheritDoc}
46 | */
47 | @Override
48 | public RecipeChoice choice() {
49 | return new RecipeChoice.MaterialChoice(this.material);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/fr/traqueur/recipes/impl/domains/ingredients/StrictItemStackIngredient.java:
--------------------------------------------------------------------------------
1 | package fr.traqueur.recipes.impl.domains.ingredients;
2 |
3 | import org.bukkit.inventory.ItemStack;
4 | import org.bukkit.inventory.RecipeChoice;
5 |
6 | /**
7 | * This class represents an ingredient that is an item stack with strict comparison
8 | */
9 | public class StrictItemStackIngredient extends ItemStackIngredient {
10 |
11 | /**
12 | * Create a new StrictItemStackIngredient
13 | * @param item The item of the ingredient
14 | * @param sign The sign of the ingredient
15 | */
16 | public StrictItemStackIngredient(ItemStack item, Character sign) {
17 | super(item, sign);
18 | }
19 |
20 | /**
21 | * Create a new StrictItemStackIngredient
22 | * @param item The item of the ingredient
23 | */
24 | public StrictItemStackIngredient(ItemStack item) {
25 | super(item);
26 | }
27 |
28 | /**
29 | * {@inheritDoc}
30 | */
31 | @Override
32 | public boolean isSimilar(ItemStack item) {
33 | return item.isSimilar(this.item);
34 | }
35 |
36 | /**
37 | * {@inheritDoc}
38 | */
39 | @Override
40 | public RecipeChoice choice() {
41 | return new RecipeChoice.ExactChoice(this.item);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/fr/traqueur/recipes/impl/domains/ingredients/TagIngredient.java:
--------------------------------------------------------------------------------
1 | package fr.traqueur.recipes.impl.domains.ingredients;
2 |
3 | import fr.traqueur.recipes.api.domains.Ingredient;
4 | import org.bukkit.Material;
5 | import org.bukkit.Tag;
6 | import org.bukkit.inventory.ItemStack;
7 | import org.bukkit.inventory.RecipeChoice;
8 |
9 | /**
10 | * This class represents an ingredient that is a tag
11 | */
12 | public class TagIngredient extends Ingredient {
13 |
14 | /**
15 | * The tag of the ingredient
16 | */
17 | private final Tag tag;
18 |
19 | /**
20 | * Create a new TagIngredient
21 | * @param tag The tag of the ingredient
22 | * @param sign The sign of the ingredient
23 | */
24 | public TagIngredient(Tag tag, Character sign) {
25 | super(sign);
26 | this.tag = tag;
27 | }
28 |
29 | /**
30 | * Create a new TagIngredient
31 | * @param tag The tag of the ingredient
32 | */
33 | public TagIngredient(Tag tag) {
34 | this(tag, null);
35 | }
36 |
37 | /**
38 | * {@inheritDoc}
39 | */
40 | @Override
41 | public boolean isSimilar(ItemStack item) {
42 | return this.tag.isTagged(item.getType());
43 | }
44 |
45 | /**
46 | * {@inheritDoc}
47 | */
48 | @Override
49 | public RecipeChoice choice() {
50 | return new RecipeChoice.MaterialChoice(this.tag);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/java/fr/traqueur/recipes/impl/domains/recipes/RecipeBuilder.java:
--------------------------------------------------------------------------------
1 | package fr.traqueur.recipes.impl.domains.recipes;
2 |
3 | import fr.traqueur.recipes.api.RecipeType;
4 | import fr.traqueur.recipes.api.domains.Ingredient;
5 | import fr.traqueur.recipes.api.domains.Recipe;
6 | import fr.traqueur.recipes.impl.domains.ItemRecipe;
7 | import org.bukkit.inventory.ItemStack;
8 |
9 | import java.util.ArrayList;
10 | import java.util.Arrays;
11 | import java.util.List;
12 |
13 | /**
14 | * This class is used to build recipes.
15 | */
16 | public class RecipeBuilder implements Recipe {
17 |
18 | /**
19 | * The list of ingredients.
20 | */
21 | private final List ingredientList = new ArrayList<>();
22 |
23 | /**
24 | * The name of the recipe.
25 | */
26 | private String name;
27 |
28 | /**
29 | * The result of the recipe.
30 | */
31 | private ItemStack result;
32 |
33 | /**
34 | * The amount of the result.
35 | */
36 | private int amount = 1;
37 |
38 | /**
39 | * The type of the recipe.
40 | */
41 | private RecipeType type;
42 |
43 | /**
44 | * The group of the recipe.
45 | */
46 | private String group = "";
47 |
48 | /**
49 | * The category of the recipe.
50 | */
51 | private String category = "";
52 |
53 | /**
54 | * The cooking time of the recipe.
55 | */
56 | private int cookingTime = 0;
57 |
58 | /**
59 | * The experience of the recipe.
60 | */
61 | private float experience = 0;
62 |
63 | /**
64 | * The pattern of the recipe.
65 | */
66 | private String[] pattern = null;
67 |
68 | /**
69 | * {@inheritDoc}
70 | */
71 | @Override
72 | public Recipe setName(String name) {
73 | if(type == null) {
74 | throw new IllegalArgumentException("Recipe type is not set");
75 | }
76 | this.name = name;
77 | return this;
78 | }
79 |
80 | /**
81 | * {@inheritDoc}
82 | */
83 | @Override
84 | public Recipe setResult(ItemStack result) {
85 | if(type == null) {
86 | throw new IllegalArgumentException("Recipe type is not set");
87 | }
88 | this.result = result;
89 | return this;
90 | }
91 |
92 | /**
93 | * {@inheritDoc}
94 | */
95 | @Override
96 | public Recipe setAmount(int amount) {
97 | this.amount = amount;
98 | return this;
99 | }
100 |
101 | /**
102 | * {@inheritDoc}
103 | */
104 | @Override
105 | public Recipe setType(RecipeType type) {
106 | this.type = type;
107 | return this;
108 | }
109 |
110 | /**
111 | * {@inheritDoc}
112 | */
113 | @Override
114 | public Recipe addIngredient(Ingredient ingredient) {
115 | if(type == null) {
116 | throw new IllegalArgumentException("Recipe type is not set");
117 | }
118 | if(type.getMaxIngredients() <= ingredientList.size()) {
119 | throw new IllegalArgumentException("Too many ingredients");
120 | }
121 |
122 | if(type == RecipeType.CRAFTING_SHAPED) {
123 | if(pattern == null) {
124 | throw new IllegalArgumentException("Pattern is not set");
125 | }
126 | if(ingredient.sign() == null) {
127 | throw new IllegalArgumentException("Ingredient sign is not set");
128 | }
129 |
130 | if(Arrays.stream(pattern)
131 | .flatMapToInt(String::chars)
132 | .noneMatch(c -> c == ingredient.sign())) {
133 | throw new IllegalArgumentException("Pattern does not contain the ingredient sign");
134 | }
135 | }
136 |
137 | this.ingredientList.add(ingredient);
138 | return this;
139 | }
140 |
141 | /**
142 | * {@inheritDoc}
143 | */
144 | @Override
145 | public Recipe setGroup(String group) {
146 | if(type == null) {
147 | throw new IllegalArgumentException("Recipe type is not set");
148 | }
149 | this.group = group;
150 | return this;
151 | }
152 |
153 | /**
154 | * {@inheritDoc}
155 | */
156 | @Override
157 | public Recipe setCategory(String category) {
158 | if(type == null) {
159 | throw new IllegalArgumentException("Recipe type is not set");
160 | }
161 | if(type == RecipeType.STONE_CUTTING) {
162 | throw new IllegalArgumentException("Category is not valid for STONE_CUTTING type");
163 | }
164 |
165 | this.category = category;
166 | return this;
167 | }
168 |
169 | /**
170 | * {@inheritDoc}
171 | */
172 | @Override
173 | public Recipe setPattern(String[] pattern) {
174 | if(type == null) {
175 | throw new IllegalArgumentException("Recipe type is not set");
176 | }
177 | if (type != RecipeType.CRAFTING_SHAPED) {
178 | throw new IllegalArgumentException("Recipe type is not a shaped recipe");
179 | }
180 | if(pattern.length > 3) {
181 | throw new IllegalArgumentException("Pattern is too long");
182 | }
183 |
184 | boolean areLengthsValid = Arrays.stream(pattern)
185 | .map(String::length)
186 | .allMatch(len -> len >= 1 && len <= 3) &&
187 | Arrays.stream(pattern)
188 | .map(String::length)
189 | .distinct()
190 | .count() == 1;
191 |
192 | if(!areLengthsValid) {
193 | throw new IllegalArgumentException("Pattern is not valid");
194 | }
195 |
196 | this.pattern = pattern;
197 | return this;
198 | }
199 |
200 | /**
201 | * {@inheritDoc}
202 | */
203 | @Override
204 | public Recipe setCookingTime(int cookingTime) {
205 | if(type == null) {
206 | throw new IllegalArgumentException("Recipe type is not set");
207 | }
208 | if(!RecipeType.smeltingRecipes().contains(type)) {
209 | throw new IllegalArgumentException("Recipe type is not a smelting recipe");
210 | }
211 | this.cookingTime = cookingTime;
212 | return this;
213 | }
214 |
215 | /**
216 | * {@inheritDoc}
217 | */
218 | @Override
219 | public Recipe setExperience(float experience) {
220 | if(type == null) {
221 | throw new IllegalArgumentException("Recipe type is not set");
222 | }
223 | if(!RecipeType.smeltingRecipes().contains(type)) {
224 | throw new IllegalArgumentException("Recipe type is not a smelting recipe");
225 | }
226 | this.experience = experience;
227 | return this;
228 | }
229 |
230 | /**
231 | * {@inheritDoc}
232 | */
233 | @Override
234 | public RecipeType getType() {
235 | return type;
236 | }
237 |
238 | /**
239 | * {@inheritDoc}
240 | */
241 | @Override
242 | public ItemRecipe build() {
243 | if (name == null) {
244 | throw new IllegalArgumentException("Name is not set");
245 | }
246 |
247 | if (result == null) {
248 | throw new IllegalArgumentException("Result is not set");
249 | }
250 |
251 | if (type == null) {
252 | throw new IllegalArgumentException("Type is not set");
253 | }
254 |
255 | return this.getItemRecipe(ingredientList, type, pattern, cookingTime, name, group, category, result, amount, experience);
256 | }
257 | }
258 |
--------------------------------------------------------------------------------
/src/main/java/fr/traqueur/recipes/impl/domains/recipes/RecipeConfiguration.java:
--------------------------------------------------------------------------------
1 | package fr.traqueur.recipes.impl.domains.recipes;
2 |
3 | import fr.traqueur.recipes.api.Base64;
4 | import fr.traqueur.recipes.api.RecipeType;
5 | import fr.traqueur.recipes.api.TagRegistry;
6 | import fr.traqueur.recipes.api.domains.Ingredient;
7 | import fr.traqueur.recipes.api.domains.Recipe;
8 | import fr.traqueur.recipes.api.hook.Hook;
9 | import fr.traqueur.recipes.impl.domains.ItemRecipe;
10 | import fr.traqueur.recipes.impl.domains.ingredients.ItemStackIngredient;
11 | import fr.traqueur.recipes.impl.domains.ingredients.MaterialIngredient;
12 | import fr.traqueur.recipes.impl.domains.ingredients.StrictItemStackIngredient;
13 | import fr.traqueur.recipes.impl.domains.ingredients.TagIngredient;
14 | import org.bukkit.Material;
15 | import org.bukkit.Tag;
16 | import org.bukkit.configuration.file.YamlConfiguration;
17 | import org.bukkit.inventory.ItemStack;
18 | import org.bukkit.inventory.recipe.CookingBookCategory;
19 | import org.bukkit.inventory.recipe.CraftingBookCategory;
20 | import org.bukkit.plugin.java.JavaPlugin;
21 |
22 | import java.util.ArrayList;
23 | import java.util.List;
24 | import java.util.Map;
25 |
26 | /**
27 | * This class is used to build recipes via yaml configuration.
28 | */
29 | public class RecipeConfiguration implements Recipe {
30 |
31 | /**
32 | * The list of ingredients.
33 | */
34 | private final List ingredientList = new ArrayList<>();
35 |
36 | /**
37 | * The name of the recipe.
38 | */
39 | private final String name;
40 |
41 | /**
42 | * The result of the recipe.
43 | */
44 | private final ItemStack result;
45 |
46 | /**
47 | * The amount of the result.
48 | */
49 | private final int amount;
50 |
51 | /**
52 | * The type of the recipe.
53 | */
54 | private final RecipeType type;
55 |
56 | /**
57 | * The group of the recipe.
58 | */
59 | private final String group;
60 |
61 | /**
62 | * The category of the recipe.
63 | */
64 | private final String category;
65 |
66 | /**
67 | * The pattern of the recipe.
68 | */
69 | private final int cookingTime;
70 |
71 | /**
72 | * The experience of the recipe.
73 | */
74 | private final float experience;
75 |
76 | /**
77 | * The pattern of the recipe.
78 | */
79 | private String[] pattern = null;
80 |
81 | /**
82 | * The constructor of the recipe.
83 | * @param plugin the plugin of the recipe.
84 | * @param name the name of the recipe.
85 | * @param configuration the configuration of the recipe.
86 | */
87 | public RecipeConfiguration(JavaPlugin plugin, String name, YamlConfiguration configuration) {
88 | this(plugin, name, "", configuration);
89 | }
90 |
91 | /**
92 | * The constructor of the recipe.
93 | * @param plugin the plugin of the recipe.
94 | * @param name the name of the recipe.
95 | * @param path the path of the recipe.
96 | * @param configuration the configuration of the recipe.
97 | */
98 | public RecipeConfiguration(JavaPlugin plugin, String name, String path, YamlConfiguration configuration) {
99 | this.name = name.replace(".yml", "");
100 | if(!path.endsWith(".") && !path.isEmpty()) {
101 | path += ".";
102 | }
103 | String strType = configuration.getString(path + "type", "ERROR");
104 | try {
105 | this.type = RecipeType.valueOf(strType.toUpperCase());
106 | } catch (IllegalArgumentException e) {
107 | throw new IllegalArgumentException("The type " + strType + " isn't valid.");
108 | }
109 | this.category = configuration.getString(path + "category", "");
110 | this.group = configuration.getString(path + "group", "");
111 | if(!this.checkGategory(this.category)) {
112 | throw new IllegalArgumentException("The category " + this.category + " isn't valid.");
113 | }
114 |
115 | if(configuration.contains(path + "pattern")) {
116 | this.pattern = configuration.getStringList(path+"pattern").toArray(new String[0]);
117 | }
118 |
119 | if(!configuration.contains(path + "ingredients")) {
120 | throw new IllegalArgumentException("The recipe " + name + " doesn't have ingredients.");
121 | }
122 |
123 | for(Map,?> ingredient : configuration.getMapList(path + "ingredients")) {
124 | String material = (String) ingredient.get("item");
125 | var objSign = ingredient.getOrDefault("sign", null);
126 | Character sign = objSign == null ? null : objSign.toString().charAt(0);
127 |
128 | String[] data = material.split(":");
129 | if(data.length == 1) {
130 | this.ingredientList.add(new MaterialIngredient(this.getMaterial(data[0]), sign));
131 | } else {
132 | Ingredient ingred = switch (data[0]) {
133 | case "material" -> new MaterialIngredient(this.getMaterial(data[1]), sign);
134 | case "tag" -> new TagIngredient(this.getTag(data[1]), sign);
135 | case "item" -> {
136 | boolean strict = this.isStrict(ingredient);
137 | if(strict) {
138 | yield new StrictItemStackIngredient(this.getItemStack(data[1]), sign);
139 | }
140 | yield new ItemStackIngredient(this.getItemStack(data[1]), sign);
141 | }
142 | default -> Hook.HOOKS.stream()
143 | .filter(hook -> hook.isEnable(plugin))
144 | .filter(hook -> hook.getPluginName().equalsIgnoreCase(data[0]))
145 | .findFirst()
146 | .orElseThrow(() -> new IllegalArgumentException("The data " + data[0] + " isn't valid."))
147 | .getIngredient(data[1], sign);
148 | };
149 | this.ingredientList.add(ingred);
150 | }
151 |
152 | }
153 |
154 | if(!configuration.contains(path + "result.item")) {
155 | throw new IllegalArgumentException("The recipe " + name + " doesn't have a result.");
156 | }
157 | String strItem = configuration.getString(path + "result.item");
158 | String[] resultParts = strItem.split(":");
159 | if(resultParts.length == 1) {
160 | this.result = this.getItemStack(resultParts[0]);
161 | } else {
162 | this.result = switch (resultParts[0]) {
163 | case "material" -> new ItemStack(this.getMaterial(resultParts[1]));
164 | case "item", "base64" -> this.getItemStack(resultParts[1]);
165 | default -> Hook.HOOKS.stream()
166 | .filter(hook -> hook.isEnable(plugin))
167 | .filter(hook -> hook.getPluginName().equalsIgnoreCase(resultParts[0]))
168 | .findFirst()
169 | .orElseThrow(() -> new IllegalArgumentException("The result " + strItem + " isn't valid."))
170 | .getItemStack(resultParts[1]);
171 | };
172 | }
173 | this.amount = configuration.getInt(path + "result.amount", 1);
174 |
175 |
176 | this.cookingTime = configuration.getInt(path + "cooking-time", 0);
177 | this.experience = (float) configuration.getDouble(path + "experience", 0d);
178 | }
179 |
180 | /**
181 | * This method is used to get Tag from the string.
182 | * @param data the data to get the tag.
183 | * @return the tag.
184 | */
185 | private Tag getTag(String data) {
186 | return TagRegistry.getTag(data).orElseThrow(() -> new IllegalArgumentException("The tag " + data + " isn't valid."));
187 | }
188 |
189 | /**
190 | * This method is used to check if the ingredient is strict.
191 | * @param ingredient the ingredient to check.
192 | */
193 | private boolean isStrict(Map,?> ingredient) {
194 | return ingredient.containsKey("strict") && (boolean) ingredient.get("strict");
195 | }
196 |
197 | /**
198 | * This method is used to get the itemstack from base64 string
199 | * @param base64itemstack the base64 item stack.
200 | * @return the item stack.
201 | */
202 | private ItemStack getItemStack(String base64itemstack) {
203 | return Base64.decodeItem(base64itemstack);
204 | }
205 |
206 | /**
207 | * This method is used to get the material from the string.
208 | * @param material the material string.
209 | * @return the material.
210 | */
211 | private Material getMaterial(String material) {
212 | try {
213 | return Material.valueOf(material.toUpperCase());
214 | } catch (IllegalArgumentException e) {
215 | throw new IllegalArgumentException("The material " + material + " isn't valid.");
216 | }
217 | }
218 |
219 | /**
220 | * This method is used to check if the category is valid.
221 | * @param category the group to check.
222 | * @return true if the category is valid.
223 | */
224 | private boolean checkGategory(String category) {
225 | category = category.toUpperCase();
226 | try {
227 | CookingBookCategory.valueOf(category);
228 | } catch (IllegalArgumentException ignored) {
229 | try {
230 | CraftingBookCategory.valueOf(category);
231 | } catch (IllegalArgumentException ignored_2) {
232 | return false;
233 | }
234 | }
235 | return true;
236 | }
237 |
238 | /**
239 | * {@inheritDoc}
240 | */
241 | @Override
242 | public Recipe setName(String name) {
243 | throw new UnsupportedOperationException("Not supported yet.");
244 | }
245 |
246 | /**
247 | * {@inheritDoc}
248 | */
249 | @Override
250 | public Recipe setResult(ItemStack result) {
251 | throw new UnsupportedOperationException("Not supported yet.");
252 | }
253 |
254 | /**
255 | * {@inheritDoc}
256 | */
257 | @Override
258 | public Recipe setAmount(int amount) {
259 | throw new UnsupportedOperationException("Not supported yet.");
260 | }
261 |
262 | /**
263 | * {@inheritDoc}
264 | */
265 | @Override
266 | public Recipe setType(RecipeType type) {
267 | throw new UnsupportedOperationException("Not supported yet.");
268 | }
269 |
270 | /**
271 | * {@inheritDoc}
272 | */
273 | @Override
274 | public Recipe addIngredient(Ingredient ingredient) {
275 | throw new UnsupportedOperationException("Not supported yet.");
276 | }
277 |
278 | /**
279 | * {@inheritDoc}
280 | */
281 | @Override
282 | public Recipe setGroup(String group) {
283 | throw new UnsupportedOperationException("Not supported yet.");
284 | }
285 |
286 | /**
287 | * {@inheritDoc}
288 | */
289 | @Override
290 | public Recipe setCategory(String category) {
291 | throw new UnsupportedOperationException("Not supported yet.");
292 | }
293 |
294 | /**
295 | * {@inheritDoc}
296 | */
297 | @Override
298 | public Recipe setPattern(String... pattern) {
299 | throw new UnsupportedOperationException("Not supported yet.");
300 | }
301 |
302 | /**
303 | * {@inheritDoc}
304 | */
305 | @Override
306 | public Recipe setCookingTime(int cookingTime) {
307 | throw new UnsupportedOperationException("Not supported yet.");
308 | }
309 |
310 | /**
311 | * {@inheritDoc}
312 | */
313 | @Override
314 | public Recipe setExperience(float experience) {
315 | throw new UnsupportedOperationException("Not supported yet.");
316 | }
317 |
318 | /**
319 | * {@inheritDoc}
320 | */
321 | @Override
322 | public RecipeType getType() {
323 | throw new UnsupportedOperationException("Not supported yet.");
324 | }
325 |
326 | /**
327 | * {@inheritDoc}
328 | */
329 | @Override
330 | public ItemRecipe build() {
331 | return this.getItemRecipe(ingredientList, type, pattern, cookingTime, name, group, category, result, amount, experience);
332 | }
333 | }
334 |
--------------------------------------------------------------------------------
/src/main/java/fr/traqueur/recipes/impl/hook/Hooks.java:
--------------------------------------------------------------------------------
1 | package fr.traqueur.recipes.impl.hook;
2 |
3 | import dev.lone.itemsadder.api.CustomStack;
4 | import fr.traqueur.recipes.api.domains.Ingredient;
5 | import fr.traqueur.recipes.api.hook.Hook;
6 | import fr.traqueur.recipes.impl.hook.hooks.ItemsAdderIngredient;
7 | import fr.traqueur.recipes.impl.hook.hooks.OraxenIngredient;
8 | import org.bukkit.inventory.ItemStack;
9 |
10 | /**
11 | * This enum is used to define the different internal hooks that can be used in the plugin.
12 | */
13 | public enum Hooks implements Hook {
14 |
15 | /**
16 | * The ItemsAdder hook.
17 | */
18 | ITEMSADDER {
19 | @Override
20 | public Ingredient getIngredient(String data, Character sign) {
21 | return new ItemsAdderIngredient(data, sign);
22 | }
23 |
24 | @Override
25 | public ItemStack getItemStack(String data) {
26 | if(!CustomStack.isInRegistry(data)) {
27 | throw new IllegalArgumentException("The item " + data + " is not registered in ItemsAdder.");
28 | }
29 | return CustomStack.getInstance(data).getItemStack();
30 | }
31 | },
32 | /**
33 | * The Oraxen hook.
34 | */
35 | ORAXEN {
36 | @Override
37 | public Ingredient getIngredient(String data, Character sign) {
38 | return new OraxenIngredient(data, sign);
39 | }
40 |
41 | @Override
42 | public ItemStack getItemStack(String data) {
43 | var builder = io.th0rgal.oraxen.api.OraxenItems.getItemById(data);
44 | if(builder == null) {
45 | throw new IllegalArgumentException("Oraxen item with id " + data + " not found");
46 | }
47 | return builder.build();
48 | }
49 | },
50 | ;
51 |
52 | /**
53 | * {@inheritDoc}
54 | */
55 | @Override
56 | public String getPluginName() {
57 | return this.name().toLowerCase();
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/main/java/fr/traqueur/recipes/impl/hook/hooks/ItemsAdderIngredient.java:
--------------------------------------------------------------------------------
1 | package fr.traqueur.recipes.impl.hook.hooks;
2 |
3 | import dev.lone.itemsadder.api.CustomStack;
4 | import fr.traqueur.recipes.api.domains.Ingredient;
5 | import org.bukkit.inventory.ItemStack;
6 | import org.bukkit.inventory.RecipeChoice;
7 |
8 | /**
9 | * This class is an implementation of the BaseIngredient class.
10 | * It is used to represent an ingredient that is an item from the ItemsAdder plugin.
11 | */
12 | public class ItemsAdderIngredient extends Ingredient {
13 |
14 | /**
15 | * The CustomStack object that represents the item from ItemsAdder.
16 | */
17 | private final CustomStack customStack;
18 |
19 | /**
20 | * Constructor of the class.
21 | * @param data The id of the item from ItemsAdder.
22 | * @param sign The sign that represents the ingredient in the recipe.
23 | */
24 | public ItemsAdderIngredient(String data, Character sign) {
25 | super(sign);
26 | this.customStack = CustomStack.getInstance(data);
27 | if(this.customStack == null) {
28 | throw new IllegalArgumentException("The item " + data + " is not registered in ItemsAdder.");
29 | }
30 | }
31 |
32 | /**
33 | * Constructor of the class.
34 | * @param data The id of the item from ItemsAdder.
35 | */
36 | public ItemsAdderIngredient(String data) {
37 | this(data,null);
38 | }
39 |
40 | /**
41 | * {@inheritDoc}
42 | */
43 | @Override
44 | public boolean isSimilar(ItemStack ingredient) {
45 | CustomStack item = CustomStack.byItemStack(ingredient);
46 | if (item == null) return false;
47 | if (!item.getNamespacedID().equals(this.customStack.getNamespacedID())) return false;
48 | return true;
49 | }
50 |
51 | /**
52 | * {@inheritDoc}
53 | */
54 | @Override
55 | public RecipeChoice choice() {
56 | return new RecipeChoice.MaterialChoice(this.customStack.getItemStack().getType());
57 | }
58 |
59 | @Override
60 | public String toString() {
61 | return this.customStack.getNamespacedID();
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/java/fr/traqueur/recipes/impl/hook/hooks/OraxenIngredient.java:
--------------------------------------------------------------------------------
1 | package fr.traqueur.recipes.impl.hook.hooks;
2 |
3 | import fr.traqueur.recipes.api.domains.Ingredient;
4 | import io.th0rgal.oraxen.api.OraxenItems;
5 | import org.bukkit.Material;
6 | import org.bukkit.inventory.ItemStack;
7 | import org.bukkit.inventory.RecipeChoice;
8 | import org.bukkit.persistence.PersistentDataContainer;
9 | import org.bukkit.persistence.PersistentDataType;
10 |
11 | /**
12 | * This class is an implementation of the BaseIngredient class.
13 | * It is used to represent an ingredient that is an item from the Oraxen plugin.
14 | */
15 | public class OraxenIngredient extends Ingredient {
16 |
17 | /**
18 | * The Material object that represents the item from Oraxen.
19 | */
20 | private final Material material;
21 | /**
22 | * The id of the item from Oraxen.
23 | */
24 | private final String id;
25 |
26 | /**
27 | * Constructor of the class.
28 | * @param id The id of the item from Oraxen.
29 | * @param sign The sign that represents the ingredient in the recipe.
30 | */
31 | public OraxenIngredient(String id, Character sign) {
32 | super(sign);
33 | var builder = OraxenItems.getItemById(id);
34 | if(builder == null) {
35 | throw new IllegalArgumentException("Oraxen item with id " + id + " not found");
36 | }
37 | this.material = builder.build().getType();
38 | this.id = id;
39 | }
40 |
41 | /**
42 | * Constructor of the class.
43 | * @param id The id of the item from Oraxen.
44 | */
45 | public OraxenIngredient(String id) {
46 | this(id, null);
47 | }
48 |
49 | /**
50 | * {@inheritDoc}
51 | */
52 | @Override
53 | public boolean isSimilar(ItemStack item) {
54 | if (!item.hasItemMeta() || item.getItemMeta().getPersistentDataContainer().isEmpty()) {
55 | return false;
56 | }
57 |
58 | if(item.getType() != material) {
59 | return false;
60 | }
61 |
62 | PersistentDataContainer container = item.getItemMeta().getPersistentDataContainer();
63 | if(container.has(OraxenItems.ITEM_ID, PersistentDataType.STRING)) {
64 | return container.getOrDefault(OraxenItems.ITEM_ID, PersistentDataType.STRING, "ERROR")
65 | .equals(id);
66 | }
67 | return false;
68 | }
69 |
70 | /**
71 | * {@inheritDoc}
72 | */
73 | @Override
74 | public RecipeChoice choice() {
75 | return new RecipeChoice.MaterialChoice(material);
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/main/java/fr/traqueur/recipes/impl/updater/Updater.java:
--------------------------------------------------------------------------------
1 | package fr.traqueur.recipes.impl.updater;
2 |
3 | import java.io.IOException;
4 | import java.net.HttpURLConnection;
5 | import java.net.URI;
6 | import java.net.URL;
7 | import java.util.Properties;
8 | import java.util.Scanner;
9 | import java.util.logging.Logger;
10 |
11 | /**
12 | * This class is used to check if the plugin is up to date
13 | */
14 | public class Updater {
15 |
16 | /**
17 | * Check updates the plugin
18 | * @param name The name of the plugin
19 | */
20 | public static void update(String name) {
21 | new Updater(name).checkUpdates();
22 | }
23 |
24 | /**
25 | * The URL of the GitHub API
26 | */
27 | private static final String API_URL = "https://api.github.com/repos/Traqueur-dev/{name}/releases/latest";
28 | /**
29 | * The name of the plugin
30 | */
31 | private final String name;
32 |
33 | /**
34 | * Create a new Updater
35 | * @param name The name of the plugin
36 | */
37 | private Updater(String name) {
38 | this.name = name;
39 | }
40 |
41 | /**
42 | * Check if the plugin is up to date and log a warning if it's not
43 | */
44 | private void checkUpdates() {
45 | if(!this.isUpToDate()) {
46 | Logger.getLogger(name)
47 | .warning("The framework is not up to date, " +
48 | "the latest version is " + this.fetchLatestVersion());
49 | }
50 | }
51 |
52 | /**
53 | * Get the version of the plugin
54 | * @return The version of the plugin
55 | */
56 | private String getVersion() {
57 | Properties prop = new Properties();
58 | try {
59 | prop.load(Updater.class.getClassLoader().getResourceAsStream("version.properties"));
60 | return prop.getProperty("version");
61 | } catch (IOException e) {
62 | throw new RuntimeException(e);
63 | }
64 | }
65 |
66 | /**
67 | * Check if the plugin is up to date
68 | * @return True if the plugin is up to date, false otherwise
69 | */
70 | private boolean isUpToDate() {
71 | try {
72 | String latestVersion = fetchLatestVersion();
73 | return getVersion().equals(latestVersion);
74 | } catch (Exception e) {
75 | return false;
76 | }
77 | }
78 |
79 | /**
80 | * Get the latest version of the plugin
81 | * @return The latest version of the plugin
82 | */
83 | private String fetchLatestVersion() {
84 | try {
85 | URL url = URI.create(API_URL.replace("{name}", this.name)).toURL();
86 | String responseString = getString(url);
87 | int tagNameIndex = responseString.indexOf("\"tag_name\"");
88 | int start = responseString.indexOf('\"', tagNameIndex + 10) + 1;
89 | int end = responseString.indexOf('\"', start);
90 | return responseString.substring(start, end);
91 | } catch (Exception e) {
92 | return null;
93 | }
94 | }
95 |
96 | /**
97 | * Get the latest version of the plugin
98 | * @return The latest version of the plugin
99 | */
100 | private String getString(URL url) throws IOException {
101 | HttpURLConnection connection = (HttpURLConnection) url.openConnection();
102 | connection.setRequestMethod("GET");
103 |
104 | StringBuilder response = new StringBuilder();
105 | try (Scanner scanner = new Scanner(connection.getInputStream())) {
106 | while (scanner.hasNext()) {
107 | response.append(scanner.nextLine());
108 | }
109 | } finally {
110 | connection.disconnect();
111 | }
112 |
113 | return response.toString();
114 | }
115 | }
--------------------------------------------------------------------------------
/src/main/resources/version.properties:
--------------------------------------------------------------------------------
1 | version=2.0.1
--------------------------------------------------------------------------------
/test-plugin/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'java'
3 | id 'io.github.goooler.shadow' version '8.1.8'
4 | }
5 |
6 | group = 'fr.traqueur'
7 | version = '1.0-SNAPSHOT'
8 |
9 | repositories {
10 | mavenCentral()
11 | maven {
12 | name = "spigotmc-repo"
13 | url = "https://hub.spigotmc.org/nexus/content/repositories/snapshots/"
14 | }
15 | maven {
16 | name = "sonatype"
17 | url = "https://oss.sonatype.org/content/groups/public/"
18 | }
19 | maven {
20 | name = "jitpack"
21 | url = "https://jitpack.io"
22 | }
23 | }
24 |
25 | dependencies {
26 | compileOnly("org.spigotmc:spigot-api:1.21.3-R0.1-SNAPSHOT")
27 | implementation rootProject
28 | }
29 |
30 | def targetJavaVersion = 21
31 | java {
32 | def javaVersion = JavaVersion.toVersion(targetJavaVersion)
33 | sourceCompatibility = javaVersion
34 | targetCompatibility = javaVersion
35 | if (JavaVersion.current() < javaVersion) {
36 | toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion)
37 | }
38 | }
39 |
40 | tasks.withType(JavaCompile).configureEach {
41 | options.encoding = 'UTF-8'
42 |
43 | if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) {
44 | options.release.set(targetJavaVersion)
45 | }
46 | }
47 |
48 | processResources {
49 | def props = [version: version]
50 | inputs.properties props
51 | filteringCharset 'UTF-8'
52 | filesMatching('plugin.yml') {
53 | expand props
54 | }
55 | }
56 |
57 | build.dependsOn shadowJar
58 |
59 | shadowJar {
60 | relocate "fr.traqueur.recipes", "fr.traqueur.testplugin.api.recipes"
61 | }
62 |
--------------------------------------------------------------------------------
/test-plugin/gradle.properties:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Traqueur-dev/RecipesAPI/7f74390966352ab15bd5bc68c6d0552eaf6bbbf1/test-plugin/gradle.properties
--------------------------------------------------------------------------------
/test-plugin/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Traqueur-dev/RecipesAPI/7f74390966352ab15bd5bc68c6d0552eaf6bbbf1/test-plugin/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/test-plugin/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/test-plugin/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | #
21 | # Gradle start up script for POSIX generated by Gradle.
22 | #
23 | # Important for running:
24 | #
25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
26 | # noncompliant, but you have some other compliant shell such as ksh or
27 | # bash, then to run this script, type that shell name before the whole
28 | # command line, like:
29 | #
30 | # ksh Gradle
31 | #
32 | # Busybox and similar reduced shells will NOT work, because this script
33 | # requires all of these POSIX shell features:
34 | # * functions;
35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
37 | # * compound commands having a testable exit status, especially «case»;
38 | # * various built-in commands including «command», «set», and «ulimit».
39 | #
40 | # Important for patching:
41 | #
42 | # (2) This script targets any POSIX shell, so it avoids extensions provided
43 | # by Bash, Ksh, etc; in particular arrays are avoided.
44 | #
45 | # The "traditional" practice of packing multiple parameters into a
46 | # space-separated string is a well documented source of bugs and security
47 | # problems, so this is (mostly) avoided, by progressively accumulating
48 | # options in "$@", and eventually passing that to Java.
49 | #
50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
52 | # see the in-line comments for details.
53 | #
54 | # There are tweaks for specific operating systems such as AIX, CygWin,
55 | # Darwin, MinGW, and NonStop.
56 | #
57 | # (3) This script is generated from the Groovy template
58 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
59 | # within the Gradle project.
60 | #
61 | # You can find Gradle at https://github.com/gradle/gradle/.
62 | #
63 | ##############################################################################
64 |
65 | # Attempt to set APP_HOME
66 |
67 | # Resolve links: $0 may be a link
68 | app_path=$0
69 |
70 | # Need this for daisy-chained symlinks.
71 | while
72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
73 | [ -h "$app_path" ]
74 | do
75 | ls=$( ls -ld "$app_path" )
76 | link=${ls#*' -> '}
77 | case $link in #(
78 | /*) app_path=$link ;; #(
79 | *) app_path=$APP_HOME$link ;;
80 | esac
81 | done
82 |
83 | # This is normally unused
84 | # shellcheck disable=SC2034
85 | APP_BASE_NAME=${0##*/}
86 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
87 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
88 |
89 | # Use the maximum available, or set MAX_FD != -1 to use that value.
90 | MAX_FD=maximum
91 |
92 | warn () {
93 | echo "$*"
94 | } >&2
95 |
96 | die () {
97 | echo
98 | echo "$*"
99 | echo
100 | exit 1
101 | } >&2
102 |
103 | # OS specific support (must be 'true' or 'false').
104 | cygwin=false
105 | msys=false
106 | darwin=false
107 | nonstop=false
108 | case "$( uname )" in #(
109 | CYGWIN* ) cygwin=true ;; #(
110 | Darwin* ) darwin=true ;; #(
111 | MSYS* | MINGW* ) msys=true ;; #(
112 | NONSTOP* ) nonstop=true ;;
113 | esac
114 |
115 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
116 |
117 |
118 | # Determine the Java command to use to start the JVM.
119 | if [ -n "$JAVA_HOME" ] ; then
120 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
121 | # IBM's JDK on AIX uses strange locations for the executables
122 | JAVACMD=$JAVA_HOME/jre/sh/java
123 | else
124 | JAVACMD=$JAVA_HOME/bin/java
125 | fi
126 | if [ ! -x "$JAVACMD" ] ; then
127 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
128 |
129 | Please set the JAVA_HOME variable in your environment to match the
130 | location of your Java installation."
131 | fi
132 | else
133 | JAVACMD=java
134 | if ! command -v java >/dev/null 2>&1
135 | then
136 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
137 |
138 | Please set the JAVA_HOME variable in your environment to match the
139 | location of your Java installation."
140 | fi
141 | fi
142 |
143 | # Increase the maximum file descriptors if we can.
144 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
145 | case $MAX_FD in #(
146 | max*)
147 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
148 | # shellcheck disable=SC2039,SC3045
149 | MAX_FD=$( ulimit -H -n ) ||
150 | warn "Could not query maximum file descriptor limit"
151 | esac
152 | case $MAX_FD in #(
153 | '' | soft) :;; #(
154 | *)
155 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
156 | # shellcheck disable=SC2039,SC3045
157 | ulimit -n "$MAX_FD" ||
158 | warn "Could not set maximum file descriptor limit to $MAX_FD"
159 | esac
160 | fi
161 |
162 | # Collect all arguments for the java command, stacking in reverse order:
163 | # * args from the command line
164 | # * the main class name
165 | # * -classpath
166 | # * -D...appname settings
167 | # * --module-path (only if needed)
168 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
169 |
170 | # For Cygwin or MSYS, switch paths to Windows format before running java
171 | if "$cygwin" || "$msys" ; then
172 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
173 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
174 |
175 | JAVACMD=$( cygpath --unix "$JAVACMD" )
176 |
177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
178 | for arg do
179 | if
180 | case $arg in #(
181 | -*) false ;; # don't mess with options #(
182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
183 | [ -e "$t" ] ;; #(
184 | *) false ;;
185 | esac
186 | then
187 | arg=$( cygpath --path --ignore --mixed "$arg" )
188 | fi
189 | # Roll the args list around exactly as many times as the number of
190 | # args, so each arg winds up back in the position where it started, but
191 | # possibly modified.
192 | #
193 | # NB: a `for` loop captures its iteration list before it begins, so
194 | # changing the positional parameters here affects neither the number of
195 | # iterations, nor the values presented in `arg`.
196 | shift # remove old arg
197 | set -- "$@" "$arg" # push replacement arg
198 | done
199 | fi
200 |
201 |
202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
204 |
205 | # Collect all arguments for the java command:
206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
207 | # and any embedded shellness will be escaped.
208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
209 | # treated as '${Hostname}' itself on the command line.
210 |
211 | set -- \
212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
213 | -classpath "$CLASSPATH" \
214 | org.gradle.wrapper.GradleWrapperMain \
215 | "$@"
216 |
217 | # Stop when "xargs" is not available.
218 | if ! command -v xargs >/dev/null 2>&1
219 | then
220 | die "xargs is not available"
221 | fi
222 |
223 | # Use "xargs" to parse quoted args.
224 | #
225 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
226 | #
227 | # In Bash we could simply go:
228 | #
229 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
230 | # set -- "${ARGS[@]}" "$@"
231 | #
232 | # but POSIX shell has neither arrays nor command substitution, so instead we
233 | # post-process each arg (as a line of input to sed) to backslash-escape any
234 | # character that might be a shell metacharacter, then use eval to reverse
235 | # that process (while maintaining the separation between arguments), and wrap
236 | # the whole thing up as a single "set" statement.
237 | #
238 | # This will of course break if any of these variables contains a newline or
239 | # an unmatched quote.
240 | #
241 |
242 | eval "set -- $(
243 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
244 | xargs -n1 |
245 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
246 | tr '\n' ' '
247 | )" '"$@"'
248 |
249 | exec "$JAVACMD" "$@"
250 |
--------------------------------------------------------------------------------
/test-plugin/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%"=="" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%"=="" set DIRNAME=.
29 | @rem This is normally unused
30 | set APP_BASE_NAME=%~n0
31 | set APP_HOME=%DIRNAME%
32 |
33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
35 |
36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
38 |
39 | @rem Find java.exe
40 | if defined JAVA_HOME goto findJavaFromJavaHome
41 |
42 | set JAVA_EXE=java.exe
43 | %JAVA_EXE% -version >NUL 2>&1
44 | if %ERRORLEVEL% equ 0 goto execute
45 |
46 | echo. 1>&2
47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
48 | echo. 1>&2
49 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
50 | echo location of your Java installation. 1>&2
51 |
52 | goto fail
53 |
54 | :findJavaFromJavaHome
55 | set JAVA_HOME=%JAVA_HOME:"=%
56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
57 |
58 | if exist "%JAVA_EXE%" goto execute
59 |
60 | echo. 1>&2
61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
62 | echo. 1>&2
63 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
64 | echo location of your Java installation. 1>&2
65 |
66 | goto fail
67 |
68 | :execute
69 | @rem Setup the command line
70 |
71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
72 |
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if %ERRORLEVEL% equ 0 goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | set EXIT_CODE=%ERRORLEVEL%
85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
87 | exit /b %EXIT_CODE%
88 |
89 | :mainEnd
90 | if "%OS%"=="Windows_NT" endlocal
91 |
92 | :omega
93 |
--------------------------------------------------------------------------------
/test-plugin/src/main/java/fr/traqueur/testplugin/TestPlugin.java:
--------------------------------------------------------------------------------
1 | package fr.traqueur.testplugin;
2 |
3 | import fr.traqueur.recipes.api.RecipeType;
4 | import fr.traqueur.recipes.api.RecipesAPI;
5 | import fr.traqueur.recipes.impl.domains.ItemRecipe;
6 | import fr.traqueur.recipes.impl.domains.recipes.RecipeBuilder;
7 | import org.bukkit.Material;
8 | import org.bukkit.inventory.ItemStack;
9 | import org.bukkit.inventory.meta.ItemMeta;
10 | import org.bukkit.plugin.java.JavaPlugin;
11 |
12 | public final class TestPlugin extends JavaPlugin {
13 |
14 | private RecipesAPI recipesAPI;
15 |
16 | @Override
17 | public void onEnable() {
18 | recipesAPI = new RecipesAPI(this, true);
19 |
20 | ItemRecipe recipe = new RecipeBuilder()
21 | .setType(RecipeType.CRAFTING_SHAPELESS)
22 | .setName("example-simple")
23 | .setResult(new ItemStack(Material.DIAMOND))
24 | .setAmount(64)
25 | .addIngredient(Material.DIRT)
26 | .build();
27 |
28 | ItemRecipe recipe2 = new RecipeBuilder()
29 | .setType(RecipeType.CRAFTING_SHAPED)
30 | .setName("example-shaped")
31 | .setResult(new ItemStack(Material.DIAMOND))
32 | .setAmount(64)
33 | .setPattern("DDD", "DID", "DDD")
34 | .addIngredient(Material.DIRT, 'D')
35 | .addIngredient(Material.DIAMOND, 'I')
36 | .build();
37 |
38 | ItemStack ingredient = new ItemStack(Material.PAPER);
39 | ItemMeta meta = ingredient.getItemMeta();
40 | meta.setDisplayName("Dirt Magic");
41 | meta.setCustomModelData(1);
42 | ingredient.setItemMeta(meta);
43 |
44 | ItemRecipe recipe3 = new RecipeBuilder()
45 | .setType(RecipeType.CRAFTING_SHAPELESS)
46 | .setName("example-complex")
47 | .setResult(new ItemStack(Material.DIAMOND))
48 | .setAmount(64)
49 | .addIngredient(ingredient)
50 | .build();
51 |
52 | ItemRecipe recipe4 = new RecipeBuilder()
53 | .setType(RecipeType.SMELTING)
54 | .setName("example-furnace")
55 | .setResult(new ItemStack(Material.DIAMOND))
56 | .setAmount(64)
57 | .addIngredient(ingredient, true)
58 | .setCookingTime(10)
59 | .build();
60 |
61 | recipesAPI.addRecipe(recipe);
62 | recipesAPI.addRecipe(recipe2);
63 | recipesAPI.addRecipe(recipe3);
64 | recipesAPI.addRecipe(recipe4);
65 | }
66 |
67 | @Override
68 | public void onDisable() {
69 | recipesAPI.unregisterRecipes();
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/test-plugin/src/main/resources/plugin.yml:
--------------------------------------------------------------------------------
1 | name: test-plugin
2 | version: '1.0-SNAPSHOT'
3 | main: fr.traqueur.testplugin.TestPlugin
4 | api-version: '1.21'
5 |
--------------------------------------------------------------------------------
/test-plugin/src/main/resources/recipes/example.yml:
--------------------------------------------------------------------------------
1 | # Type of the recipe
2 | # CRAFTING_SHAPED, CRAFTING_SHAPELESS, SMELTING, SMITHING_TRANSFORM
3 | # SMOKING, BLASTING, CAMPFIRE_COOKING, STONE_CUTTING
4 | type: CRAFTING_SHAPED
5 |
6 | # Not for Smithing recipes
7 | group: example
8 |
9 | # Not for Smithing recipes
10 | # FOOD, BLOCKS, MISC for smelting recipes
11 | # BUILDING, REDSTONE, EQUIPMENT, MISC for crafting recipes
12 | category: MISC
13 | pattern:
14 | - "###"
15 | - "##"
16 | result:
17 | item: "material:diamond"
18 | amount: 64
19 | ingredients:
20 | - item: "material:stone"
21 | sign: '#'
22 |
23 | # Only for smelt recipes
24 | #cooking-time: 100
25 | # Only for smelt recipes
26 | #experience: 100
--------------------------------------------------------------------------------