├── .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 | ![Release](https://img.shields.io/github/v/tag/Traqueur-dev/RecipesAPI?label=latest&sort=semver) 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>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 --------------------------------------------------------------------------------