diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index e0e8353..a50cacd 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -9,74 +9,155 @@ name: Java CI with Gradle on: push: - branches: [ "master" ] + branches: [ "v1.x.x", "v2.x.x" ] pull_request: - branches: [ "master" ] + branches: [ "v1.x.x", "v2.x.x" ] + release: + types: [published] + +concurrency: + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }} + cancel-in-progress: true jobs: build: - - runs-on: ubuntu-latest - permissions: - contents: read - - steps: - - uses: actions/checkout@v4 - - name: Set up JDKs - uses: actions/setup-java@v4 - with: - java-version: | - 16 - 17 - 20 - 21 - distribution: 'temurin' - cache: 'gradle' - - # Configure Gradle for optimal use in GitHub Actions, including caching of downloaded dependencies. - # See: https://github.com/gradle/actions/blob/main/setup-gradle/README.md - - name: Setup Gradle - uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0 - - - name: Make gradlew executable - run: chmod +x ./gradlew - - name: Build with Gradle Wrapper - run: ./gradlew build - - - name: Summarize tests results - uses: jeantessier/test-summary-action@v1 - if: ${{ always() }} - - # NOTE: The Gradle Wrapper is the default and recommended way to run Gradle (https://docs.gradle.org/current/userguide/gradle_wrapper.html). - # If your project does not have the Gradle Wrapper configured, you can use the following configuration to run Gradle with a specified version. - # - # - name: Setup Gradle - # uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0 - # with: - # gradle-version: '8.9' - # - # - name: Build with Gradle 8.9 - # run: gradle build - - dependency-submission: - runs-on: ubuntu-latest permissions: contents: write - steps: - - uses: actions/checkout@v4 - - name: Set up JDKs - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: | - 16 - 17 - 20 - 21 + env: + MODRINTH_VERSIONS: '["1.18.x", "1.19.x", "1.20.x", "1.21.x", "26.1.x", "26.2.x"]' + MODRINTH_PLATFORMS: '["spigot", "paper", "purpur", "folia"]' - # Generates and submits a dependency graph, enabling Dependabot Alerts for all project dependencies. - # See: https://github.com/gradle/actions/blob/main/dependency-submission/README.md - - name: Generate and submit dependency graph - uses: gradle/actions/dependency-submission@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0 + steps: + - uses: actions/checkout@v4 + - name: Set up JDKs + uses: actions/setup-java@v4 + with: + java-version: | + 21 + distribution: 'temurin' + cache: gradle + + - name: Cache Gradle root and wrapper + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: gradle-root-${{ runner.os }}-${{ hashFiles('build.gradle*') }} + restore-keys: gradle-root-${{ runner.os }}- + + # Setup paperweight cache + - name: Cache paperweight + uses: actions/cache@v3 + with: + path: | + ./nms/*/.gradle/caches/paperweight + key: paperweight-submodules-${{ runner.os }}-${{ hashFiles('nms/*/build.gradle*') }} + restore-keys: | + paperweight-submodules-${{ runner.os }}- + + # Configure Gradle for optimal use in GitHub Actions, including caching of downloaded dependencies. + # See: https://github.com/gradle/actions/blob/main/setup-gradle/README.md + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + + - name: Make gradlew executable + run: chmod +x ./gradlew + + - name: Get small commit hash + if: ${{ github.event_name != 'release' && success() }} + run: echo "SMALL_COMMIT_HASH=$(git rev-parse --short ${{ github.sha }})" >> $GITHUB_ENV + + - name: Build with Gradle Wrapper + run: ./gradlew build --parallel --stacktrace + + # only submit dependency on push + - name: Generate and submit dependency graph + uses: gradle/actions/dependency-submission@v4 + if: ${{ github.event_name == 'push' && success() }} + continue-on-error: true + + # Get the names of the online and offline jars + # grep -v "offline" to exclude offline jar as the regex would catch it otherwise + - name: Get file name for jars + run: | + ONLINE_JAR_PATH=$(ls build/libs/CustomAnvil-*.jar | grep -v "offline") + OFFLINE_JAR_PATH=$(ls build/libs/CustomAnvil-*-offline.jar) + + echo "ONLINE_JAR_NAME=$(basename $ONLINE_JAR_PATH)" >> $GITHUB_ENV + echo "OFFLINE_JAR_NAME=$(basename $OFFLINE_JAR_PATH)" >> $GITHUB_ENV + + # upload the named jars as artifact + - name: Upload online JAR artifact + uses: actions/upload-artifact@v4 + with: + name: CustomAnvil.jar + path: build/libs/${{ env.ONLINE_JAR_NAME }} + + - name: Upload offline JAR file + uses: actions/upload-artifact@v4 + with: + name: CustomAnvil-offline.jar + path: build/libs/${{ env.OFFLINE_JAR_NAME }} + + - name: Summarize tests results + uses: jeantessier/test-summary-action@v1 + if: ${{ always() }} + + # upload the jar to release + - name: Upload jar to release + if: ${{ github.event_name == 'release' && success() }} + uses: softprops/action-gh-release@v2 + with: + files: | + build/libs/${{ env.ONLINE_JAR_NAME }} + build/libs/${{ env.OFFLINE_JAR_NAME }} + + - name: Hangar release + if: ${{ (github.event_name != 'release' || github.event_name != 'push') && github.repository_owner == 'alexcrea' && success() }} + env: + HANGAR_API_TOKEN: ${{ secrets.HANGAR_API_TOKEN }} + RELEASE_CHANGELOG: ${{ github.event.release.body }} + run: ./gradlew publishAllPublicationsToHangar --stacktrace + + - name: Modrinth publish alpha + if: ${{ github.event_name == 'push' && github.repository_owner == 'alexcrea' && success() }} + uses: cloudnode-pro/modrinth-publish@v2 + with: + token: ${{ secrets.MODRINTH_TOKEN }} + project: S75Ueiq9 + name: dev-${{ env.SMALL_COMMIT_HASH }} + version: dev-${{ env.SMALL_COMMIT_HASH }} + loaders: ${{ env.MODRINTH_PLATFORMS }} + game-versions: ${{ env.MODRINTH_VERSIONS }} + channel: alpha + files: build/libs/${{ env.ONLINE_JAR_NAME }} + changelog: ${{ github.event.head_commit.message }} + + - name: Modrinth publish release + if: ${{ github.event_name == 'release' && github.repository_owner == 'alexcrea' && success() }} + uses: cloudnode-pro/modrinth-publish@v2 + with: + token: ${{ secrets.MODRINTH_TOKEN }} + project: S75Ueiq9 + name: ${{ github.event.release.name }} + version: ${{ github.event.release.tag_name }}${{ github.event.release.prerelease && '-pre' || '' }} + loaders: ${{ env.MODRINTH_PLATFORMS }} + game-versions: ${{ env.MODRINTH_VERSIONS }} + channel: ${{ github.event.release.prerelease == false && 'release' || 'beta' }} + files: build/libs/${{ env.ONLINE_JAR_NAME }} + changelog: ${{ github.event.release.body }} + + - name: Send release note to discord + if: ${{ github.event_name == 'release' && github.repository_owner == 'alexcrea' && success() }} + uses: tsickert/discord-webhook@v7.0.0 + with: + webhook-url: ${{ secrets.RELEASE_WEBHOOK_URL }} + content: | + ${{ github.event.release.prerelease == false && '<@&1338546156325568642>' || '<@&1352296092989001768>' }} + # New ${{ github.event.release.prerelease && 'beta ' || '' }}version of custom anvil ! <:CustomAnvil:1262550667986342001>([Modrinth](https://modrinth.com/plugin/customanvil), [Hangar](https://hangar.papermc.io/alexcrea/CustomAnvil) and [GitHub](${{ github.event.release.html_url }}) links) + -# note: automated release. spigot is not uploaded yet. + + ${{ github.event.release.body }} diff --git a/.gitignore b/.gitignore index b005983..982299c 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,9 @@ /impl/*/build /impl/*/.gradle +# run folder +/run/ + # other random folders /htmlReport -/.kotlin/errors \ No newline at end of file +/.kotlin/errors diff --git a/.run/Server.run.xml b/.run/Server.run.xml new file mode 100644 index 0000000..9a8887f --- /dev/null +++ b/.run/Server.run.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md new file mode 100644 index 0000000..9d37521 --- /dev/null +++ b/COMPATIBILITY.md @@ -0,0 +1,63 @@ +### Bedrock issue +For server using geyser, bedrock player cannot use custom "recipes" in the anvil. +This is cannot be fixed on geyser or my side. + +### Plugin Compatibility +Here is various plugins that had issues with CustomAnvil +where efforts was made for compatibility and should be working right: + +some if not all of them are cool ! I recommend checking them out ! + +## Supported By CustomAnvil +These plugins have compatibility handled by custom anvil. seek help on custom anvil and do not bother these developers + +#### Enchantment Plugins +- [ExcellentEnchants](https://www.spigotmc.org/resources/excellentenchants-%E2%AD%90-75-vanilla-like-enchantments.61693/) by NightExpress: + Use ExcellentEnchants item type \ + Also use ExcellentEnchant max enchant limit + +- [EcoEnchant](https://www.spigotmc.org/resources/ecoenchants-%E2%AD%95-250-enchantments-%E2%9C%85-create-custom-enchants-%E2%9C%A8-essentials-cmi-support.79573/) by Auxilor: + Need to use /anvilconfigreload or a server restart to add newly added enchantment. + Use EcoEnchant restriction system but new restriction can be added in custom anvil + +- [Enchantment²](https://www.spigotmc.org/resources/enchants-squared-the-enchantsplus-rewrite-custom-enchantments-that-act-like-vanilla-ones.86747/) by Athlaeos: + Support by Custom Anvil but still experimental. Automatic configuration. Plugin is not actively developed anymore + +- [SuperEnchants](https://modrinth.com/plugin/superenchants) by Aznos: + Use SuperEnchant restrictions system but new restriction can be added in custom anvil + + +#### Custom Items Plugins +Custom Items support is considered unstable. If you find issue please report it ! + +- [EcoItem](https://www.spigotmc.org/resources/30-sale%E2%8F%B3-ecoitems-%E2%AD%95-create-custom-items-%E2%9C%85-weapons-armors-tools-charms-%E2%9C%A8-item-levels-rarities.94601/) by Exanthiax: + May have some issue. but should partially work I hope + +- [ItemAdder](https://www.spigotmc.org/resources/%E2%9C%A8itemsadder%E2%AD%90emotes-mobs-items-armors-hud-gui-emojis-blocks-wings-hats-liquids.73355/) by LoneDev: + Need to fix unit item not working completly correctly as in can't have twice same base item + +#### Anvil Mechanics Plugins +- [Disenchantment](https://www.spigotmc.org/resources/disenchantment-1-21-1-1-20-6-new-book-splitting-mechanics.110741/) by H7KZ + Partially use Custom Anvil maximum XP settings (>= 6.1.5) + +- [HavenBags](https://www.spigotmc.org/resources/havenbags-shulker-like-player-bound-bags-1-17-1-21-4.110420/) by hyperdefined + For bag upgrade and skin via anvil. (version >= 1.31.0) + +- [AxPlayerWarp](https://modrinth.com/project/QDJHDKvi) by ArtillexStudios + For its anvil inventory usage + +- [ToolsStats](https://modrinth.com/project/oBZj9E15) by Valorless + For token application using anvil + +### Known Partially Incompatible +- [UberEnchant](https://modrinth.com/plugin/uberenchant) by coltonj96 + Anvil handling as they are doing something similar to CustomAnvil. +It is by no mean there faults and I recommend checking them out especially if custom anvil do not work for your use case ! + +- [AdvencedEnchantments](https://ae.advancedplugins.net/) by Advanced Plugins + Paid plugin I do not own as I did not get commissioned for support. + may be able to use api but cannot test on my side + +If you like Custom Anvil to support a specific plugin (custom enchant or anvil mechanic). +You can ask, but please note implementing compatibility will be considered +as low priority as I work for the plugin as an hobby on my free time for free. diff --git a/CREDITS.md b/CREDITS.md new file mode 100644 index 0000000..56409f5 --- /dev/null +++ b/CREDITS.md @@ -0,0 +1,34 @@ +Thanks **DelilahEve** for making [Unsafe Enchants](https://github.com/DelilahEve/UnsafeEnchants). \ +CustomAnvil was initially a fork of Unsafe Enchants where I wanted to add more and more and here we are now ! + +Thanks for all the contributors of bukkit, spigot, the paper team and the adventure API developers \ +Thanks JetBrain for making IntelliJ + +### Dependencies +These dependencies (or a modified version of) are used by custom anvil +- [IF](https://github.com/stefvanschie/IF) an inventory framework by stefvanschie +- [Mockbukkit](https://github.com/MockBukkit/MockBukkit) for unit testing +- [CentralPortalPlus](https://github.com/lalakii/central-portal-plus) by lalakii +- [test-summary-action](https://github.com/jeantessier/test-summary-action) by jeantessier +- [modrinth-publish](https://github.com/cloudnode-pro/modrinth-publish) by Zefir +- [discord-webhook](https://github.com/tsickert/discord-webhook) by tsickert +- Thanks [bstats](https://bstats.org/) for keeping me motivated +- And [FastStats](https://faststats.dev/) alternative to bstats in beta test +- [ModrinthUpdateChecker](https://github.com/Clickism/ModrinthUpdateChecker) by Clickism and thanks to the modrinth team + +### Compatibility +Thanks to all the cool creator making the minecraft plugin's ecosystem works ! \ +See [Compatibility list](https://github.com/alexcrea/CustomAnvil/blob/v1.x.x/COMPATIBILITY.md) for details + +but especially, Big Thanks for H7KZ maker of [Disenchantment](https://github.com/H7KZ/Disenchantment) + +### Special Thanks + +Thanks for all the users trying my plugin for these niche use cases +, reporting issues and giving ideas ! + +Thanks coltonj96 for [UberEnchant](https://modrinth.com/plugin/uberenchant). +we may be incompatible with the anvil, but I do think it is a good alternative ! \ +I wish one day to work on cross compatibiltiy \ +* If custom anvil do not work well for you or your use case give it a try ! * + diff --git a/README.md b/README.md index 0af6b69..9d9e678 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,11 @@ # Custom Anvil **Custom Anvil** is a plugin that allows server administrators to customize every aspect of the anvil's mechanics. -It is expected to work on 1.18 to 1.21.3 minecraft servers running spigot or paper. -(the plugin support of 1.16.5 to 1.17.1 is experimental and may encounter issues) - -**Custom Anvil** was previously named **Unsafe Enchants+**. -It was renamed because it now affects every anvil aspect and not only unsafe enchants\ -**Custom Anvil** is based on [Unsafe Enchants](https://github.com/DelilahEve/UnsafeEnchants) by DelilahEve. ### Download Locations: the plugin can be downloaded on -[Spigot](https://www.spigotmc.org/resources/custom-anvil.114884), - [modrinth](https://modrinth.com/plugin/customanvil), + [Modrinth](https://modrinth.com/plugin/customanvil), [Hangar](https://hangar.papermc.io/alexcrea/CustomAnvil) or here [on GitHub](https://github.com/alexcrea/CustomAnvil/releases/latest) @@ -21,94 +14,98 @@ the plugin can be downloaded on - Vanilla like default configuration. - Custom enchantment level limit. - Custom anvil recipes. -- Custom enchant restrictions (allow unsafe enchantment only for a group of item or create new restriction). +- Custom enchant restrictions (allows unsafe enchantment only for a group of item or create new restriction). - Custom items of unit repairs (repair damaged with unit of "material", for example the repair of diamond sword by diamonds). - Custom XP cost for every aspect of the anvil. - Permissions to bypass level limit or enchantment restriction. - Display XP cost instead of "too expensive" when above level 40. (see below for more information) - Can handle some custom enchantment plugins (see below for more information) - Gui to configure the plugin in game. -- Support of color code and hexadecimal color +- Support use of color code, hexadecimal color and minimessage for color/decoration - (Experimental) Folia support (gui do not work) +- (Experimental) Dialog rename (allows longer rename) +- (Experimental) Anvil with monetary cost (using vault) (require dialog rename) + +And more ! + --- ### Permissions: +Note that for most of them you also need to enable feature and in most case enable use of permission for the specfic feature (indicated with `(toggleable)`) ```yml +# Generic and bypass permissions ca.affected: Player with this permission will be affected by the plugin ca.bypass.fuse: Allow player to combine every enchantments to every item (no custom limit) ca.bypass.level: Allow player to bypass every level limit (no custom limit) -ca.command.reload: Allow administrator to reload the plugin's configs -ca.config.edit: Allow administrator to edit the plugin's config in game -# Bellow permissions also require some config change to allow usage of features -# usage of these permission is toggleable in basic config gui or config.yml -# Permissions related to use of color -ca.color.code: Allow player to use color code if enabled (toggleable) -ca.color.hex: Allow player to use hexadecimal color if enabled (toggleable) +# Command permissions +ca.command.reload: Allow administrator to reload the plugin's configs +ca.command.diagnostic: Allow adminastator to diagnistic some simple problem with the plugin +ca.config.edit: Allow administrator to edit the plugin's config in game + +# ----------------------------------------------------------------------------- +# Bellow permissions also require some config change to allow usage of features +# Usage of these permission is toggleable in basic config gui or config.yml +# ----------------------------------------------------------------------------- +# Permissions related to use of color and minimessage +ca.color.code: Allow player to use color code on rename if enabled (toggleable) +ca.color.code.[thecode] (for example ca.color.code.a): Allows usage of only certain color code (toggleable) +ca.color.hex: Allow player to use hexadecimal color on rename if enabled (toggleable) +ca.rename.minimessage: Allow player to use minimessage formating on rename if enabled (toggleable) # Permissions related to edition of the lore ca.lore_edit.book: Allow player to edit lore via book and quil if enabled (toggleable) ca.lore_edit.paper: Allow player to edit lore via paper if enabled (toggleable) + +# Others +ca.rename.dialog: Allow player to use the rename dialog (toggleable) ``` ### Commands -```yml -anvilconfigreload or carl: Reload every config of this plugin -customanvilconfig or configanvil: open a menu for administrator to edit plugin's config in game -``` + +run `/customanvil help` to get information about available commands \ +this only show subcommands you have permission for + ### Supported Plugins -Custom Anvil can be compatible with some custom enchantments and anvil mechanics plugins. - -Here is a list of supported custom enchantment plugins with support status: -- [Enchantment²](https://www.spigotmc.org/resources/enchants-squared-the-enchantsplus-rewrite-custom-enchantments-that-act-like-vanilla-ones.86747/): -Support by Custom Anvil but still experimental. Automatic configuration. - -- [EcoEnchant](https://www.spigotmc.org/resources/ecoenchants-%E2%AD%95-250-enchantments-%E2%9C%85-create-custom-enchants-%E2%9C%A8-essentials-cmi-support.79573/): -Support by Custom Anvil but still experimental. Need to use /anvilconfigreload or a server restart to add newly added enchantment. -Use EcoEnchant restriction system by default. - -- [ExcellentEnchants](https://www.spigotmc.org/resources/excellentenchants-%E2%AD%90-75-vanilla-like-enchantments.61693/): -Support by Custom Anvil but still experimental. Use ExcellentEnchants item type. - -- [Superenchants](https://modrinth.com/plugin/superenchants) -support by Superenchants. Use CustomAnvil to combine enchantment in anvil in survival. - -Here is a list of supported anvil mechanic plugins with support status: -- [Disenchantment](https://www.spigotmc.org/resources/disenchantment-1-21-1-1-20-6-new-book-splitting-mechanics.110741/) -support by Custom Anvil but still experimental. Mostly use Custom Anvil basic XP settings. (version >= 6.1.5) - -- [HavenBags](https://www.spigotmc.org/resources/havenbags-shulker-like-player-bound-bags-1-17-1-21-4.110420/) -support by Custom Anvil. Not really enchantment related but CustomAnvil should not impact bag upgrade and skin via anvil. (version >= 1.31.0) - -If you like Custom Anvil to support a specific plugin (custom enchant or anvil mechanic). -You can ask, but please note implementing compatibility will be considered -as low priority as I work for the plugin on my free time for free. +See the [Compatibility list](https://github.com/alexcrea/CustomAnvil/blob/v1.x.x/COMPATIBILITY.md) ### Overriding Too Expensive One of the configurations allow displaying price about 40 and removing Too Expensive. \ By how the minecraft client work: price above 40 can only be displayed green, even if the player does not own enough experience level. -Minecraft version 1.17 to 1.21.1 do not need any dependency. Other version need ProtocoLib enabled on your server for this feature. \ -You can also wait for an update of the plugin to support a newer version. - -Please note that 1.16.5 to 1.17.1 are not officially supported. Run at your own risk. +spigot version 1.18 to 1.21.11 do not need any ProtocoLib dependency. (26.1.0 or above requires it) \ +Any recent paper version also are supported for this feature. +But you should wait for update for new version containing new enchantable item or new enchantments if any of this got added. +Else it is, likely, fine to use the current version you are ussing on a new paper version ### For custom enchantment plugin developers For information about the API, please refer to [the Wiki](https://github.com/alexcrea/CustomAnvil/wiki) \ -(Please note that the wiki is currently incomplete)​ +(Please note that the wiki is currently incomplete) --- ### Default Plugin's Configurations -For 1.18 to 1.20.6 use the [1.18 configurations](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.18)\ -For 1.21 to 1.21.1 use the [1.21 configurations](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21) +see [Here](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs) --- -Custom anvil [use bstat](https://bstats.org/plugin/bukkit/Unsafe%20Enchants%20Plus/20923) for metric. You can [disable it](https://bstats.org/getting-started) if you like. +### Metric And Telemetry +Custom anvil [use bstat](https://bstats.org/plugin/bukkit/Unsafe%20Enchants%20Plus/20923) +and [faststats](https://faststats.dev/project/customanvil/minecraft-plugin) for metric and error reporting. + +You can select specific telemetry or disable all in config.yml. \ +You can also [disable bstat](https://bstats.org/getting-started) and [faststats](https://faststats.dev/info) in their /plugin folder if you like too. + +faststats is in beta testing please report me or them any error you encounter + +### Credits and Thanks +Credits and thanks can be seen [here](https://github.com/alexcrea/CustomAnvil/blob/v1.x.x/CREDITS.md) ### Planned: - Better Folia support (make gui work. fix some dirty handled parts) -- Get restriction on unknown enchantments +- Get restriction on unknown enchantments (planned for V2) - More features for custom anvil craft ### Known issue: Most unknown registered enchantments (by unsupported custom enchantment plugin & datapacks) will not have restriction by default. Planned but no eta. + +### Do you need help with the plugin, or have any issue or suggestion? +You can ask on the discussion page, create a [GitHub issue](https://github.com/alexcrea/CustomAnvil/issues) or join my [discord](https://discord.gg/KHUNsUfRYJ)​ diff --git a/build.gradle.kts b/build.gradle.kts index db69e47..a5dc7c3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,33 +1,68 @@ import cn.lalaki.pub.BaseCentralPortalPlusExtension import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import groovy.util.Node +import groovy.util.NodeList +import io.papermc.hangarpublishplugin.model.HangarPublication +import io.papermc.hangarpublishplugin.model.Platforms import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import java.io.ByteArrayOutputStream plugins { - kotlin("jvm") version "2.1.0" + kotlin("jvm") version "2.3.0" java id("org.jetbrains.dokka").version("1.9.20") - id("com.gradleup.shadow").version("8.3.5") + id("com.gradleup.shadow").version("9.3.0") // Maven publish `maven-publish` signing - id("cn.lalaki.central").version("1.2.5") + id("cn.lalaki.central").version("1.2.8") // Paper - id("io.papermc.paperweight.userdev") version "2.0.0-beta.14" apply false + id("io.papermc.paperweight.userdev") version "2.0.0-beta.17" apply false + id("io.papermc.hangar-publish-plugin") version "0.1.2" } group = "xyz.alexcrea" -version = "1.10.0-beta.4" +version = "1.17.5" + +val isDevBuild = System.getenv("SMALL_COMMIT_HASH") != null +val isPreRelease = System.getenv("IS_GITHUB_PRERELEASE") == "true" + +val effectiveVersion = "$version" + + (if (isDevBuild) "-dev-${System.getenv("SMALL_COMMIT_HASH")!!}" else "") repositories { // EcoEnchants maven(url = "https://repo.auxilor.io/repository/maven-public/") + // ExcellentEnchants + maven(url = "https://repo.nightexpressdev.com/releases") + + // ItemsAdder + maven(url = "https://maven.devs.beer/") + + // For fast stats + maven { + name = "thenextlvlReleases" + url = uri("https://repo.thenextlvl.net/releases") + } + + // For vault unlocked + maven { url = uri("https://repo.codemc.io/repository/creatorfromhell/") } } +val reobfNMS = providers.gradleProperty("subprojects.reobfnms") + .get().split(",") + dependencies { // Spigot api compileOnly("org.spigotmc:spigot-api:1.18-R0.1-SNAPSHOT") + // fast stats + implementation("dev.faststats.metrics:bukkit:0.27.0") + + // minimessage + implementation("net.kyori:adventure-text-minimessage:4.25.0") + // Gui library val inventoryFramework = "xyz.alexcrea.cuanvil.inventoryframework:IF-CustomAnvil:0.10.18.2" implementation(inventoryFramework) @@ -36,15 +71,22 @@ dependencies { // EnchantsSquaredRewritten compileOnly(files("libs/EnchantsSquared.jar")) - // EcoEnchants - compileOnly("com.willfp:EcoEnchants:12.11.1") + // EcoEnchants & item + compileOnly("com.willfp:libreforge:4.79.0:all") compileOnly("com.willfp:eco:6.74.5") + + compileOnly("com.willfp:EcoEnchants:12.11.1") compileOnly(project(":impl:LegacyEcoEnchant")) + compileOnly("com.willfp:EcoItems:5.66.0") + // ExcellentEnchants - compileOnly(files("libs/nightcore-2.7.3.jar")) - compileOnly(files("libs/ExcellentEnchants-4.3.1.jar")) - compileOnly(files("libs/ExcellentEnchants 4.1.0-striped.jar")) // For legacy excellent enchants + implementation(project(":impl:ExcellentEnchant5_4")) + compileOnly("su.nightexpress.excellentenchants:Core:5.1.0") { + exclude("org.spigotmc") + } + compileOnly(files("libs/ExcellentEnchants-4.3.3-striped.jar")) // For pre v5 excellent enchants + compileOnly(files("libs/ExcellentEnchants-4.1.0-striped.jar")) // For legacy excellent enchants // Disenchantment compileOnly(files("libs/Disenchantment-6.1.5.jar")) @@ -52,27 +94,33 @@ dependencies { // HavenBags compileOnly(files("libs/HavenBags-1.31.0.1760.jar")) + // ToolStats + compileOnly(files("libs/toolstats-1.9.6-stripped.jar")) + + // AxPlayerWarps + compileOnly(files("libs/AxPlayerWarps-1.10.3.jar")) + + // SuperEnchants + compileOnly(files("libs/SuperEnchants-4.6.2-all.jar")) + + // ItemsAdder API + compileOnly("dev.lone:api-itemsadder:4.0.10") + + // Vault api + compileOnly("net.milkbowl.vault:VaultUnlockedAPI:2.16") + // Include nms implementation(project(":nms:nms-common")) - implementation(project(":nms:v1_17R1", configuration = "reobf")) - implementation(project(":nms:v1_18R1", configuration = "reobf")) - implementation(project(":nms:v1_18R2", configuration = "reobf")) - implementation(project(":nms:v1_19R1", configuration = "reobf")) - implementation(project(":nms:v1_19R2", configuration = "reobf")) - implementation(project(":nms:v1_19R3", configuration = "reobf")) - implementation(project(":nms:v1_20R1", configuration = "reobf")) - implementation(project(":nms:v1_20R2", configuration = "reobf")) - implementation(project(":nms:v1_20R3", configuration = "reobf")) - implementation(project(":nms:v1_20R4", configuration = "reobf")) - implementation(project(":nms:v1_21R1", configuration = "reobf")) - implementation(project(":nms:v1_21R2", configuration = "reobf")) - implementation(project(":nms:v1_21R3", configuration = "reobf")) + implementation(project(":nms:nms-paper")) + for (nmsPart in reobfNMS) { + implementation(project(":nms:$nmsPart", configuration = "reobf")) + } // include kotlin for the offline jar implementation(kotlin("stdlib")) // Test dependency - testImplementation("org.mockbukkit.mockbukkit:mockbukkit-v1.21:4.37.0") + testImplementation("org.mockbukkit.mockbukkit:mockbukkit-v1.21:4.48.0") testRuntimeOnly("commons-lang:commons-lang:2.6") } @@ -94,8 +142,9 @@ allprojects { compileOnly(kotlin("stdlib")) // Test dependency - testImplementation(platform("org.junit:junit-bom:5.11.3")) + testImplementation(platform("org.junit:junit-bom:5.12.2")) testImplementation("org.junit.jupiter:junit-jupiter") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") } tasks.getByName("test") { @@ -110,7 +159,8 @@ allprojects { // Set target version tasks.withType().configureEach { - sourceCompatibility = "16" // We aim for java 16 for minecraft 1.16.5. even if it not really suported by custom anvil. + sourceCompatibility = + "16" // We aim for java 16 for minecraft 1.16.5. even if it not really supported by custom anvil. targetCompatibility = "16" options.encoding = "UTF-8" @@ -118,43 +168,29 @@ allprojects { kotlin { compilerOptions { - apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0) + apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2) jvmTarget.set(JvmTarget.JVM_16) } } } -// Fat-jar builder -val fatJar = tasks.register("fatJar") { - manifest { - attributes.apply { put("Main-Class", "io.delilaheve.CustomAnvil") } - } - archiveFileName.set("${rootProject.name}-${project.version}.jar") - exclude("META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA") - duplicatesStrategy = DuplicatesStrategy.WARN - from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) }) - with(tasks.jar.get() as CopySpec) -} tasks { - // Online jar (use of libraries) - shadowJar { - // No suffix for this jar - archiveClassifier.set("") - // Exclude kotlin std and its annotation - exclude("**/kotlin-stdlib*.jar") - exclude("**/annotations*.jar") + fun ShadowJar.configureBaseShadow(suffix: String, libraries: Array) { + val processedSuffix = if(suffix.isEmpty()) "" else "-$suffix" + val name = "${rootProject.name}-${effectiveVersion}${processedSuffix}.jar" + archiveFileName.set(name) // Shadow necessary dependency - relocate("com.github.stefvanschie.inventoryframework", "xyz.alexcrea.inventoryframework") + relocate("com.github.stefvanschie.inventoryframework", "xyz.alexcrea.cuanvil.inventoryframework") + relocate("dev.faststats", "xyz.alexcrea.cuanvil.faststats") - // Replace version and example fields in plugin.yml filesMatching("plugin.yml") { expand( - "version" to project.version, - "libraries" to " \"org.jetbrains.kotlin:kotlin-stdlib:2.1.0\" " + "version" to effectiveVersion + processedSuffix, + "libraries" to libraries.joinToString(transform = { "\"$it\"" }), ) } @@ -162,35 +198,28 @@ tasks { dependsOn(processResources) } - // Offline jar (include kotlin std in the final jar fine) - val offlineJar by // Shadow necessary dependency - registering( + // Online jar (use of libraries) + shadowJar { + configureBaseShadow("", + arrayOf( + "org.jetbrains.kotlin:kotlin-stdlib:2.3.0", + "net.kyori:adventure-text-minimessage:4.25.0", + "net.kyori:adventure-text-serializer-plain:4.25.0", + "net.kyori:adventure-text-serializer-legacy:4.25.0", + )) - // Include all project other dependencies - ShadowJar + // Exclude kotlin std, annotations and adventure api + exclude("*kotlin/**") + exclude("**/annotations/**") + exclude("net/kyori/**") + } - // Add custom anvil compiled - ::class, fun ShadowJar.() { - archiveClassifier.set("offline") + val offlineJar by registering(ShadowJar::class) { + configureBaseShadow("offline", emptyArray()) - // Shadow necessary dependency - relocate("com.github.stefvanschie.inventoryframework", "xyz.alexcrea.inventoryframework") - - filesMatching("plugin.yml") { - expand( - "version" to "${project.version}-offline", - "libraries" to "" - ) - } - - // Include all project other dependencies - from(project.configurations.runtimeClasspath) - - // Add custom anvil compiled - from(sourceSets.main.get().output) - - dependsOn(processResources) - }) + from(sourceSets.main.get().output) + configurations = listOf(project.configurations.runtimeClasspath.get()) + } // Make the online and offline jar on build named("build") { @@ -243,6 +272,10 @@ object Meta { const val snapshot = "https://s01.oss.sonatype.org/content/repositories/snapshots/" } +val disallowedDependency = HashSet() +disallowedDependency.addAll(reobfNMS) +disallowedDependency.addAll(listOf("nms-common", "nms-paper", "kotlin-stdlib")) + publishing { repositories { maven { @@ -258,6 +291,16 @@ publishing { from(components["kotlin"]) artifact(tasks["sourcesJar"]) artifact(tasks["javadocJar"]) + + versionMapping { + usage("java-api") { + fromResolutionOf("runtimeClasspath") + } + usage("java-runtime") { + fromResolutionResult() + } + } + pom { name.set(project.name) description.set(Meta.desc) @@ -290,7 +333,123 @@ publishing { issueManagement { url.set("https://github.com/${Meta.githubRepo}/issues") } + + withXml { + val dependenciesNode = (asNode().get("dependencies") as NodeList)[0] as Node + + val toRemove = ArrayList() + for (child in dependenciesNode.children()) { + val artifactNode = ((child as Node).get("artifactId") as NodeList)[0] as Node + val artifactID = artifactNode.value() as String + + if(disallowedDependency.contains(artifactID)) { + toRemove.add(child) + } + } + + for (node in toRemove) { + dependenciesNode.remove(node) + } + } } } } } + +// hangar publish + +fun executeGitCommand(vararg command: String): String { + val byteOut = ByteArrayOutputStream() + exec { + commandLine = listOf("git", *command) + standardOutput = byteOut + } + return byteOut.toString(Charsets.UTF_8.name()).trim() +} + + +fun latestCommitMessage(): String { + return executeGitCommand("log", "-1", "--pretty=%B") +} + +fun changelog(isOnline: Boolean): String { + var changelog = if(isDevBuild) latestCommitMessage() + else System.getenv("RELEASE_CHANGELOG") + + if(!isOnline) { + changelog = "This is an offline version of the plugin. \\\n" + + "This mean that this plugin libraries are shaded into this plugin \\\n" + + "You likely want to use the normal version of this plugin\n\n" + changelog + } + + if(changelog == null || changelog.isEmpty()) { + changelog = "empty changelog" + } + + return changelog +} + +hangarPublish { + + fun HangarPublication.configure(isOnline: Boolean, devChannel: String, releaseChannel: String) { + var versionName = effectiveVersion + if(isPreRelease) versionName+= "-pre" + if(!isOnline) versionName+= "-offline" + + version.set(versionName) + channel.set(if (isDevBuild || isPreRelease) devChannel else releaseChannel) + + changelog.set(changelog(isOnline)) + id.set("CustomAnvil") + apiKey.set(System.getenv("HANGAR_API_TOKEN")) + + platforms { + register(Platforms.PAPER) { + // Set the JAR file to upload + var task = if(isOnline) tasks.shadowJar + else tasks.named("offlineJar") + + jar.set(task.flatMap { it.archiveFile }) + + // Set platform versions from gradle.properties file + val versions: List = (property("paperVersion") as String) + .split(",") + .map { it.trim() } + platformVersions.set(versions) + + dependencies { + hangar("ProtocolLib") { + required.set(false) + } + url("Disenchantment", "https://modrinth.com/plugin/disenchantment") { + required.set(false) + } + url("ToolStats", "https://modrinth.com/plugin/toolstats") { + required.set(false) + } + url("HavenBags", "https://www.spigotmc.org/resources/havenbags-shulker-like-player-bound-bags-1-17-1-21-4.110420/") { + required.set(false) + } + url("EcoEnchants", "https://www.spigotmc.org/resources/ecoenchants-%E2%AD%95-250-enchantments-%E2%9C%85-create-custom-enchants-%E2%9C%A8-essentials-cmi-support.79573/") { + required.set(false) + } + hangar("EnchantsSquared") { + required.set(false) + } + url("ExcellentEnchants", "https://www.spigotmc.org/resources/excellentenchants-%E2%AD%90-75-vanilla-like-enchantments.61693/") { + required.set(false) + } + } + } + } + } + + publications.register("plugin") { + configure(true, "DevSnapshot", "Release") + } + + publications.register("offline") { + configure(false, "OfflineSnapshot", "OfflineRelease") + } + +} diff --git a/defaultconfigs/1.18/config.yml b/defaultconfigs/1.18/config.yml index 9c1ad67..b5ad3b0 100644 --- a/defaultconfigs/1.18/config.yml +++ b/defaultconfigs/1.18/config.yml @@ -3,6 +3,19 @@ # You can still manually edit here if you like to. but if you do, don't forget to /anvilconfigreload after you changes ! # +# What service of metric should custom anvil use +# Custom anvil collect generic information like server minecraft version, type, etc... +# It can also collect error information if error is happening (currently faststats only) +# It can also be disabled +# Please refer to README for public metric link +# Possible options: auto, bstat, faststats, disabled (auto by default) +metric_type: auto + +# Allow to report errors made caused by this plugin (only for faststats) +# This allows me to fix potentials issue that I'm not aware of +# Accept true or false (true by default) +metric_collect_errors: true + # All anvil cost will be capped to limit_repair_value if enabled. # # In other words: @@ -59,8 +72,22 @@ sacrifice_illegal_enchant_cost: 1 # # Color code are prefixed by "&" and hexadecimal color by "#". # Color code will not be applied if it colors nothing. "&&" can be used to write "&". +# For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ allow_color_code: false allow_hexadecimal_color: false +allow_minimessage: false + +# This enables restricting color code for player having specific permission +# It requires allow_color_code enabled for... obvious reasons +# +# For example: if player want to use "&aHello" it will be required that the player has +# the permission "ca.color.code.a" as he used the color code "a" +# In general permission to give to the player is "ca.color.code.[code]" +# where [code] is the color code you wish to allow the player +# +# It is kinda of useless when minimessage is supported as players would be able to bypass +# that using the equivalent minimessage tag +per_color_code_permission: false # Toggle if color should only be applicable if the player a certain permission. # @@ -72,10 +99,23 @@ permission_needed_for_color: true # Valid values include 0 to 1000. use_of_color_cost: 0 -# Default limit to apply to any enchants missing from enchant_limits +# Dialogue rename menu make use of dialog menu to allow bigger rename +# You can also change the maximum size and set it to -1 or less for maximum # -# Valid values include 1 to 1000 -default_limit: 5 +# This feature only work on paper 1.21.7 or later +# +# At the moment only english is available for this menu... sorry ! +# +# CustomAnvil use "ca.rename.dialog" when permission +enable_dialog_rename: false +dialog_rename_max_size: 256 +permission_needed_for_dialog_rename: false + +# This allows custom anvil to not "guess" the text used for rename but store it in the item +# It will make item stackable only and only if it had used the same rename text +# +# For practical reason. this only work when dialog rename is enabled +dialog_rename_keep_user_text: true # Override limits for specific enchants # @@ -83,7 +123,8 @@ default_limit: 5 # # Overrides provided default from aqua_affinity to depth_strider won't change effect with extra levels # -# Valid range of 1 - 255 for each enchantment +# Valid range of 0 - 255 for each enchantment +# -1 mean keep default enchant_limits: minecraft:aqua_affinity: 1 minecraft:binding_curse: 1 @@ -265,11 +306,20 @@ enchant_values: # Even if disable-merge-over of unbreaking is set to 2 # -1 mean enchantment merge for this enchantment is not disabled. default to -1 if absent. disable-merge-over: - # Sharpness is set to -1. it equivalent to it not being set to anything (and work as vanilla) + # Sharpness is set to -1. it equivalent to it not being set to anything (and work as vanilla on default configuration) minecraft:sharpness: -1 # If uncommented. 2 unbreaking II book would not give an unbreaking III book. but unbreaking III book can still be applied #minecraft:unbreaking: 2 +# The maximum number of enchantment an item can get. -1 for infinity +# Use eco enchant enchant_limit if present by default unless "default" is not equal to -1 +enchantment_count_limit: + default: -1 + # Limit for specific items. example bellow is an example with stick + # Per item enchantment limit override eco enchant enchant_limit and default limit + items: + stick: -1 + # Settings for lore modification lore_edit: book_and_quil: @@ -292,9 +342,12 @@ lore_edit: # # Color code are prefixed by "&" and hexadecimal color by "#" # Color code will not be applied if it colors nothing. "&&" can be used to write "&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + # + # Note that currently minimessage would disable hex code when adding color allow_color_code: true - allow_hexadecimal_color: true - use_cost: 0 + allow_hexadecimal_color: false + allow_minimessage: true remove: # If removing lore using book & quil is enabled @@ -309,16 +362,25 @@ lore_edit: shared_additive: false # If removing the lore consume the book & quil do_consume: false - # If the color should get back to color code or hex format - remove_color_on_remove: true # Cost of replacing colors remove_color_cost: 0 + # Allowed some color and tags to be reverted to plain text + # Custom anvil will prioritise format that result is a smaller resulting text + # Note that not allowing certain format will lead to some lost of color or tags. + # If configuration are exact as append appending this book should result in the exact same color + # + # Color code will be prefixed by "&" and hexadecimal color by "#". + # If color code is allowed, "&" in the text will get converted to "&&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + allow_color_code: true + allow_hexadecimal_color: false + allow_minimessage: true paper: # Permission is ca.lore_edit.paper use_permission: true # what order should the lines should get added/removed (start/end, if invalid or not present will be end) - order: "end" + order: end append_line: # If adding lore line using paper is enabled @@ -335,8 +397,12 @@ lore_edit: # # Color code are prefixed by "&" and hexadecimal color by "#" # Color code will not be applied if it colors nothing. "&&" can be used to write "&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + # + # Note that currently minimessage would disable hex code when adding color allow_color_code: true - allow_hexadecimal_color: true + allow_hexadecimal_color: false + allow_minimessage: true color_use_cost: 0 remove_line: @@ -350,10 +416,46 @@ lore_edit: shared_additive: false # If removing the lore line consume the paper do_consume: false - # If the color should get back to color code or hex format - remove_color_on_remove: true # Cost of replacing colors remove_color_cost: 0 + # Allowed some color and tags to be reverted to plain text + # Custom anvil will prioritise format that result is a smaller resulting text + # Note that not allowing certain format will lead to some lost of color or tags. + # If configuration are exact as append appending this paper should result in the exact same color + # + # Color code will be prefixed by "&" and hexadecimal color by "#". + # If color code is allowed, "&" in the text will get converted to "&&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + allow_color_code: true + allow_hexadecimal_color: false + allow_minimessage: true + +# Allow to replace the xp cost by a monetary cost +# If enabled it will not be bound to the experience level limits +# +# It also requires to enable dialog rename (set "enable_dialog_rename: false" a bit higher) +# If dialog rename permission is enabled and player do not have the permission merge will fall back to vanilla xp cost +# +# If you are using custom craft I recommend using Linear Xp Cost with Exact Linear Xp as normal Xp Cost will act "weird" +# But Linear Xp will act as 1$ time global multiplier. In other word: like you expect +# +# As this feature require dialog rename, it can only be enabled starting with paper 1.21.6 and later +monetary_cost: + enabled: false + # If using vault unlocked this allow to specify what currency should be used for anvil usage + # default being the default currency + currency: default + # multiply the anvil cost by a value to allow to have price a big bigger than like 40 + multipliers: + # global multipliers. all usage type will be multiplied by this value + global: 1.0 + # usage specific type. it will only apply for specific xp "reason" + enchantment: 1.0 # related to enchantments level + repair: 1.0 # for repairing via unit repair (per unit) + rename: 1.0 # for renaming the item + lore_edit: 1.0 # for changing the lore of the item (only if lore edit is enabled) + work_penalty: 1.0 # for work penalty (aka use penalty) + recipe: 1.0 # for custom anvil recipe cost # Whether to show debug logging debug_log: false @@ -361,10 +463,4 @@ debug_log: false # Whether to show verbose debug logging debug_log_verbose: false -# In case something when wrong with CustomAnvil packet manager. -# If you see "missing class exception" or similar you may test this. -# If enabled and Protocolib absent or disabled "Replace to expensive" will not work. -# ProtocoLib may also be used if the server is in an "unsupported" version even if this option is disabled. -force_protocolib: false - -configVersion: 1.8.0 +configVersion: 1.11.0 diff --git a/defaultconfigs/1.18/enchant_conflict.yml b/defaultconfigs/1.18/enchant_conflict.yml index 0e8b3f3..45d62c3 100644 --- a/defaultconfigs/1.18/enchant_conflict.yml +++ b/defaultconfigs/1.18/enchant_conflict.yml @@ -92,6 +92,10 @@ restriction_loyalty: enchantments: [ minecraft:loyalty ] notAffectedGroups: [ enchanted_book, trident ] +restriction_luck_of_the_sea: + enchantments: [ minecraft:luck_of_the_sea ] + notAffectedGroups: [ enchanted_book, fishing_rod ] + restriction_lure: enchantments: [ minecraft:lure ] notAffectedGroups: [ enchanted_book, fishing_rod ] diff --git a/defaultconfigs/1.21.11/README.md b/defaultconfigs/1.21.11/README.md new file mode 100644 index 0000000..a33a09b --- /dev/null +++ b/defaultconfigs/1.21.11/README.md @@ -0,0 +1,6 @@ +### Default Plugin's Configurations For 1.21.11 +- [config.yml](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21.11/config.yml) +- [enchant_conflict.yml](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21.11/enchant_conflict.yml) +- [item_groups.yml](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21.11/item_groups.yml) +- [unit_repair_item.yml](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21.11/unit_repair_item.yml) +- [custom_recipes.yml](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21.11/custom_recipes.yml) diff --git a/defaultconfigs/1.21.11/config.yml b/defaultconfigs/1.21.11/config.yml new file mode 100644 index 0000000..44885b7 --- /dev/null +++ b/defaultconfigs/1.21.11/config.yml @@ -0,0 +1,487 @@ +# +# It is recommended that you use /configanvil to edit theses config. +# You can still manually edit here if you like to. but if you do, don't forget to /anvilconfigreload after you changes ! +# + +# What service of metric should custom anvil use +# Custom anvil collect generic information like server minecraft version, type, etc... +# It can also collect error information if error is happening (currently faststats only) +# It can also be disabled +# Please refer to README for public metric link +# Possible options: auto, bstat, faststats, disabled (auto by default) +metric_type: auto + +# Allow to report errors made caused by this plugin (only for faststats) +# This allows me to fix potentials issue that I'm not aware of +# Accept true or false (true by default) +metric_collect_errors: true + +# All anvil cost will be capped to limit_repair_value if enabled. +# +# In other words: +# For any anvil cost greater than limit_repair_value, Cost will be set to limit_repair_value. +limit_repair_cost: false + +# Max cost value the Anvil can get to. +# +# Valid values include 0 to 1000. +# Cost will be displayed as "Too Expensive": +# - If Cost is above 39 +# - And replace_too_expensive is disabled (false) +limit_repair_value: 39 + +# Whether the anvil's cost limit should be removed entirely. +# +# The anvil will still visually display "Too Expensive" if "replace_too_expensive" is disabled +# However, the action will be completable if xp requirement is meet. +remove_repair_limit: false + +# Whenever anvil cost is above 39 should display the true price and not "Too Expensive". +# +# However, when bypassing "Too Expensive", anvil price will be displayed as Green. +# If the action is not completable, the cost will still be displayed as "Too expensive". +# That mean you also need to change other settings like remove_repair_limit or limit_repair_cost. +# +# Require ProtocoLib. +replace_too_expensive: false + +# XP Level amount added to the anvil when the item is repaired by another item of the same type +# +# Valid values include 0 to 1000 +item_repair_cost: 2 + +# XP Level amount added to the anvil when the item is renamed +# +# Valid values include 0 to 1000 +item_rename_cost: 1 + +# XP Level amount added to the anvil when the item is repaired by an "unit" +# For example: a Diamond on a Diamond Sword +# What's considered unit for what can be edited on the unit repair configuration. +# +# Valid values include 0 to 1000 +unit_repair_cost: 1 + +# XP Level amount added to the anvil when a sacrifice enchantment +# conflict with one of the left item enchantment +# +# Valid values include 0 to 1000 +sacrifice_illegal_enchant_cost: 1 + +# Allow using color code and hexadecimal color. +# +# Color code are prefixed by "&" and hexadecimal color by "#". +# Color code will not be applied if it colors nothing. "&&" can be used to write "&". +# For minimessage search for minimessage formating https://docs.papermc.io/adventure/minimessage/format/ +# Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin +# but any global tag will be allowed later when v2 release +allow_color_code: false +allow_hexadecimal_color: false +allow_minimessage: false + +# This enables restricting color code for player having specific permission +# It requires allow_color_code enabled for... obvious reasons +# +# For example: if player want to use "&aHello" it will be required that the player has +# the permission "ca.color.code.a" as he used the color code "a" +# In general permission to give to the player is "ca.color.code.[code]" +# where [code] is the color code you wish to allow the player +# +# It is kinda of useless when minimessage is supported as players would be able to bypass +# that using the equivalent minimessage tag +per_color_code_permission: false + +# Toggle if color should only be applicable if the player a certain permission. +# +# permission are "ca.color.code" for use of color code and "ca.color.hex" for use of hexadecimal color. +permission_needed_for_color: true + +# Xp cost if the player use color in the items name on rename. +# +# Valid values include 0 to 1000. +use_of_color_cost: 0 + +# Dialogue rename menu make use of dialog menu to allow bigger rename +# You can also change the maximum size and set it to -1 or less for maximum +# +# This feature only work on paper 1.21.7 or later +# +# At the moment only english is available for this menu... sorry ! +# +# CustomAnvil use "ca.rename.dialog" when permission +enable_dialog_rename: false +dialog_rename_max_size: 256 +permission_needed_for_dialog_rename: false + +# This allows custom anvil to not "guess" the text used for rename but store it in the item +# It will make item stackable only and only if it had used the same rename text +# +# For practical reason. this only work when dialog rename is enabled +dialog_rename_keep_user_text: true + +# Override limits for specific enchants +# +# Enchantments not listed here will use the value of default_limit +# +# Overrides provided default from aqua_affinity to depth_strider won't change effect with extra levels +# +# Valid range of 0 - 255 for each enchantment +# -1 mean keep default +enchant_limits: + minecraft:aqua_affinity: 1 + minecraft:binding_curse: 1 + minecraft:channeling: 1 + minecraft:flame: 1 + minecraft:infinity: 1 + minecraft:mending: 1 + minecraft:multishot: 1 + minecraft:silk_touch: 1 + minecraft:vanishing_curse: 1 + minecraft:depth_strider: 3 # anything more than 3 is treated as 3 by the game + minecraft:protection: 4 + minecraft:fire_protection: 4 + minecraft:blast_protection: 4 + minecraft:projectile_protection: 4 + minecraft:feather_falling: 4 + minecraft:thorns: 3 + minecraft:respiration: 3 + minecraft:sharpness: 5 + minecraft:smite: 5 + minecraft:bane_of_arthropods: 5 + minecraft:knockback: 2 + minecraft:fire_aspect: 2 + minecraft:looting: 3 + minecraft:sweeping: 3 + minecraft:sweeping_edge: 3 + minecraft:efficiency: 5 + minecraft:unbreaking: 3 + minecraft:fortune: 3 + minecraft:power: 5 + minecraft:punch: 2 + minecraft:luck_of_the_sea: 3 + minecraft:lure: 3 + minecraft:frost_walker: 2 + minecraft:impaling: 5 + minecraft:riptide: 3 + minecraft:loyalty: 3 + minecraft:piercing: 4 + minecraft:quick_charge: 3 + minecraft:soul_speed: 3 + minecraft:swift_sneak: 3 + minecraft:density: 5 + minecraft:breach: 4 + minecraft:wind_burst: 3 + minecraft:lunge: 3 + +# Multipliers used to calculate the enchantment's value in repair/combining +# +# Values here are pulled from the fandom wiki: +# https://minecraft.fandom.com/wiki/Anvil_mechanics#Costs_for_combining_enchantments +# +# If an enchantment is missing values here, or is less than 0, it will default to 0 +# +# Calculated as: [Enchantment lvl] * [multiplier] +# +# With default values protection 4 would have a value of 4 when +# coming from either a book (4 * 1) or an item (4 * 1) +enchant_values: + minecraft:aqua_affinity: + item: 4 + book: 2 + minecraft:bane_of_arthropods: + item: 2 + book: 1 + minecraft:binding_curse: + item: 8 + book: 4 + minecraft:blast_protection: + item: 4 + book: 2 + minecraft:channeling: + item: 8 + book: 4 + minecraft:depth_strider: + item: 4 + book: 2 + minecraft:efficiency: + item: 1 + book: 1 + minecraft:flame: + item: 4 + book: 2 + minecraft:feather_falling: + item: 2 + book: 1 + minecraft:fire_aspect: + item: 4 + book: 2 + minecraft:fire_protection: + item: 2 + book: 1 + minecraft:fortune: + item: 4 + book: 2 + minecraft:frost_walker: + item: 4 + book: 2 + minecraft:impaling: + item: 4 + book: 2 + minecraft:infinity: + item: 8 + book: 4 + minecraft:knockback: + item: 2 + book: 1 + minecraft:looting: + item: 4 + book: 2 + minecraft:loyalty: + item: 1 + book: 1 + minecraft:luck_of_the_sea: + item: 4 + book: 2 + minecraft:lure: + item: 4 + book: 2 + minecraft:mending: + item: 4 + book: 2 + minecraft:multishot: + item: 4 + book: 2 + minecraft:piercing: + item: 1 + book: 1 + minecraft:power: + item: 1 + book: 1 + minecraft:projectile_protection: + item: 2 + book: 1 + minecraft:protection: + item: 1 + book: 1 + minecraft:punch: + item: 4 + book: 2 + minecraft:quick_charge: + item: 2 + book: 1 + minecraft:respiration: + item: 4 + book: 2 + minecraft:riptide: + item: 4 + book: 2 + minecraft:silk_touch: + item: 8 + book: 4 + minecraft:sharpness: + item: 1 + book: 1 + minecraft:smite: + item: 2 + book: 1 + minecraft:soul_speed: + item: 8 + book: 4 + minecraft:swift_sneak: + item: 8 + book: 4 + minecraft:sweeping: + item: 4 + book: 2 + minecraft:sweeping_edge: + item: 4 + book: 2 + minecraft:thorns: + item: 8 + book: 4 + minecraft:unbreaking: + item: 2 + book: 1 + minecraft:vanishing_curse: + item: 8 + book: 4 + minecraft:density: + item: 2 + book: 1 + minecraft:breach: + item: 4 + book: 2 + minecraft:wind_burst: + item: 4 + book: 2 + minecraft:lunge: + item: 2 + book: 1 + +# Disable enchantment merging for level above the set value +# Enchantment merging is when, for example, 2 unbreaking II book combine to give sharpness III +# But Enchantment above this value can still be applied. following the previous example, we could still apply a unbreaking III book to a sword +# Even if disable-merge-over of unbreaking is set to 2 +# -1 mean enchantment merge for this enchantment is not disabled. default to -1 if absent. +disable-merge-over: + # Sharpness is set to -1. it equivalent to it not being set to anything (and work as vanilla on default configuration) + minecraft:sharpness: -1 +# If uncommented. 2 unbreaking II book would not give an unbreaking III book. but unbreaking III book can still be applied +# minecraft:unbreaking: 2 + +# The maximum number of enchantment an item can get. -1 for infinity +# Use eco enchant enchant_limit if present by default unless "default" is not equal to -1 +enchantment_count_limit: + default: -1 + # Limit for specific items. example bellow is an example with stick + # Per item enchantment limit override eco enchant enchant_limit and default limit + items: + stick: -1 + +# Settings for lore modification +lore_edit: + book_and_quil: + # Permission is ca.lore_edit.book + use_permission: true + append: + # If adding lore using book & quil is enabled + enabled: false + # Cost used every time + fixed_cost: 1 + # Cost used for every lore line added + per_line_cost: 0 + # Use left item vanilla cost penalty if any + shared_increase: false + # Increase shared left item cost penalty + shared_additive: false + # If adding the lore consume the book & quil + do_consume: false + # Allow using color code and hexadecimal color when editing lore via book & quil + # + # Color code are prefixed by "&" and hexadecimal color by "#" + # Color code will not be applied if it colors nothing. "&&" can be used to write "&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + # + # Note that currently minimessage would disable hex code when adding color + allow_color_code: true + allow_hexadecimal_color: false + allow_minimessage: true + use_cost: 0 + + remove: + # If removing lore using book & quil is enabled + enabled: false + # Cost used every time + fixed_cost: 1 + # Cost used for every lore line removed + per_line_cost: 0 + # Use left item vanilla cost penalty if any + shared_increase: false + # Increase shared left item cost penalty + shared_additive: false + # If removing the lore consume the book & quil + do_consume: false + # Cost of replacing colors + remove_color_cost: 0 + # Allowed some color and tags to be reverted to plain text + # Custom anvil will prioritise format that result is a smaller resulting text + # Note that not allowing certain format will lead to some lost of color or tags. + # If configuration are exact as append appending this book should result in the exact same color + # + # Color code will be prefixed by "&" and hexadecimal color by "#". + # If color code is allowed, "&" in the text will get converted to "&&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + allow_color_code: true + allow_hexadecimal_color: false + allow_minimessage: true + + paper: + # Permission is ca.lore_edit.paper + use_permission: true + # what order should the lines should get added/removed (start/end, if invalid or not present will be end) + order: end + + append_line: + # If adding lore line using paper is enabled + enabled: false + # Cost used every time + fixed_cost: 1 + # Use left item vanilla cost penalty if any + shared_increase: false + # Increase shared left item cost penalty + shared_additive: false + # If adding the lore line consume the paper + do_consume: false + # Allow using color code and hexadecimal color when editing lore via book & quil + # + # Color code are prefixed by "&" and hexadecimal color by "#" + # Color code will not be applied if it colors nothing. "&&" can be used to write "&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + # + # Note that currently minimessage would disable hex code when adding color + allow_color_code: true + allow_hexadecimal_color: false + allow_minimessage: true + color_use_cost: 0 + use_cost: 0 + + remove_line: + # If removing lore line using paper is enabled + enabled: false + # Cost used every time + fixed_cost: 1 + # Use left item vanilla cost penalty if any + shared_increase: false + # Increase shared left item cost penalty + shared_additive: false + # If removing the lore line consume the paper + do_consume: false + # Cost of replacing colors + remove_color_cost: 0 + # Allowed some color and tags to be reverted to plain text + # Custom anvil will prioritise format that result is a smaller resulting text + # Note that not allowing certain format will lead to some lost of color or tags. + # If configuration are exact as append appending this paper should result in the exact same color + # + # Color code will be prefixed by "&" and hexadecimal color by "#". + # If color code is allowed, "&" in the text will get converted to "&&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + allow_color_code: true + allow_hexadecimal_color: false + allow_minimessage: true + +# Allow to replace the xp cost by a monetary cost +# If enabled it will not be bound to the experience level limits +# +# It also requires to enable dialog rename (set "enable_dialog_rename: false" a bit higher) +# If dialog rename permission is enabled and player do not have the permission merge will fall back to vanilla xp cost +# +# If you are using custom craft I recommend using Linear Xp Cost with Exact Linear Xp as normal Xp Cost will act "weird" +# But Linear Xp will act as 1$ time global multiplier. In other word: like you expect +# +# As this feature require dialog rename, it can only be enabled starting with paper 1.21.6 and later +monetary_cost: + enabled: false + # If using vault unlocked this allow to specify what currency should be used for anvil usage + # default being the default currency + currency: default + # multiply the anvil cost by a value to allow to have price a big bigger than like 40 + multipliers: + # global multipliers. all usage type will be multiplied by this value + global: 1.0 + # usage specific type. it will only apply for specific xp "reason" + enchantment: 1.0 # related to enchantments level + repair: 1.0 # for repairing via unit repair (per unit) + rename: 1.0 # for renaming the item + lore_edit: 1.0 # for changing the lore of the item (only if lore edit is enabled) + work_penalty: 1.0 # for work penalty (aka use penalty) + recipe: 1.0 # for custom anvil recipe cost + +# Whether to show debug logging +debug_log: false + +# Whether to show verbose debug logging +debug_log_verbose: false + +configVersion: 1.15.5 +lowMinecraftVersion: 1.21.11 diff --git a/defaultconfigs/1.21.11/custom_recipes.yml b/defaultconfigs/1.21.11/custom_recipes.yml new file mode 100644 index 0000000..57c2220 --- /dev/null +++ b/defaultconfigs/1.21.11/custom_recipes.yml @@ -0,0 +1,5 @@ +# ---------------------------------------------------- +# This config file is to store custom craft +# It is recommended to use the in game config editor for this configuration. +# /customanvilconfig With ca.config.edit permission +# ---------------------------------------------------- diff --git a/defaultconfigs/1.21.11/enchant_conflict.yml b/defaultconfigs/1.21.11/enchant_conflict.yml new file mode 100644 index 0000000..3d62dae --- /dev/null +++ b/defaultconfigs/1.21.11/enchant_conflict.yml @@ -0,0 +1,398 @@ +# +# It is recommended that you use /configanvil to edit theses config. +# You can still manually edit here if you like to. but if you do, don't forget to /anvilconfigreload after you changes ! +# + +# material conflicts +# +# If you want to edit this file: +# - A conflict will apply to every item except if in one of the notAffectedGroups group +# - the conflict will count only if the user try to combine at least as +# many conflicting enchantment as "maxEnchantmentBeforeConflict" +# +# +# ---------------------------------------------------- +# These restriction are about not allowing enchantment +# on illegal items +# ---------------------------------------------------- + +restriction_aqua_affinity: + enchantments: + - minecraft:aqua_affinity + notAffectedGroups: + - enchanted_book + - helmets + +restriction_bane_of_arthropods: + enchantments: + - minecraft:bane_of_arthropods + notAffectedGroups: + - enchanted_book + - melee_weapons + - mace + +restriction_blast_protection: + enchantments: + - minecraft:blast_protection + notAffectedGroups: + - enchanted_book + - armors + +restriction_channeling: + enchantments: + - minecraft:channeling + notAffectedGroups: + - enchanted_book + - trident + +restriction_binding_curse: + enchantments: + - minecraft:binding_curse + notAffectedGroups: + - enchanted_book + - wearable + +restriction_vanishing_curse: + enchantments: + - minecraft:vanishing_curse + notAffectedGroups: + - enchanted_book + - can_vanish + +restriction_depth_strider: + enchantments: + - minecraft:depth_strider + notAffectedGroups: + - enchanted_book + - boots + +restriction_efficiency: + enchantments: + - minecraft:efficiency + notAffectedGroups: + - enchanted_book + - tools + - shears + +restriction_feather_falling: + enchantments: + - minecraft:feather_falling + notAffectedGroups: + - enchanted_book + - boots + +restriction_fire_aspect: + enchantments: + - minecraft:fire_aspect + notAffectedGroups: + - enchanted_book + - swords + - mace + - spears + +restriction_fire_protection: + enchantments: + - minecraft:fire_protection + notAffectedGroups: + - enchanted_book + - armors + +restriction_flame: + enchantments: + - minecraft:flame + notAffectedGroups: + - enchanted_book + - bow + +restriction_fortune: + enchantments: + - minecraft:fortune + notAffectedGroups: + - enchanted_book + - tools + +restriction_frost_walker: + enchantments: + - minecraft:frost_walker + notAffectedGroups: + - enchanted_book + - boots + +restriction_impaling: + enchantments: + - minecraft:impaling + notAffectedGroups: + - enchanted_book + - trident + +restriction_infinity: + enchantments: + - minecraft:infinity + notAffectedGroups: + - enchanted_book + - bow + +restriction_knockback: + enchantments: + - minecraft:knockback + notAffectedGroups: + - enchanted_book + - swords + - spears + +restriction_looting: + enchantments: + - minecraft:looting + notAffectedGroups: + - enchanted_book + - swords + - spears + +restriction_loyalty: + enchantments: + - minecraft:loyalty + notAffectedGroups: + - enchanted_book + - trident + +restriction_luck_of_the_sea: + enchantments: + - minecraft:luck_of_the_sea + notAffectedGroups: + - enchanted_book + - fishing_rod + +restriction_lure: + enchantments: + - minecraft:lure + notAffectedGroups: + - enchanted_book + - fishing_rod + +restriction_mending: + enchantments: + - minecraft:mending + notAffectedGroups: + - enchanted_book + - can_unbreak + +restriction_minecraft_multishot: + enchantments: + - minecraft:multishot + notAffectedGroups: + - enchanted_book + - crossbow + +restriction_piercing: + enchantments: + - minecraft:piercing + notAffectedGroups: + - enchanted_book + - crossbow + +restriction_power: + enchantments: + - minecraft:power + notAffectedGroups: + - enchanted_book + - bow + +restriction_projectile_protection: + enchantments: + - minecraft:projectile_protection + notAffectedGroups: + - enchanted_book + - armors + +restriction_protection: + enchantments: + - minecraft:protection + notAffectedGroups: + - enchanted_book + - armors + +restriction_punch: + enchantments: + - minecraft:punch + notAffectedGroups: + - enchanted_book + - bow + +restriction_quick_charge: + enchantments: + - minecraft:quick_charge + notAffectedGroups: + - enchanted_book + - crossbow + +restriction_respiration: + enchantments: + - minecraft:respiration + notAffectedGroups: + - enchanted_book + - helmets + +restriction_riptide: + enchantments: + - minecraft:riptide + notAffectedGroups: + - enchanted_book + - trident + +restriction_sharpness: + enchantments: + - minecraft:sharpness + notAffectedGroups: + - enchanted_book + - melee_weapons + +restriction__silk_touch: + enchantments: + - minecraft:silk_touch + notAffectedGroups: + - enchanted_book + - tools + +restriction_smite: + enchantments: + - minecraft:smite + notAffectedGroups: + - enchanted_book + - melee_weapons + - mace + +restriction_soul_speed: + enchantments: + - minecraft:soul_speed + notAffectedGroups: + - enchanted_book + - boots + +restriction_sweeping_edge: + enchantments: + - minecraft:sweeping + - minecraft:sweeping_edge + notAffectedGroups: + - enchanted_book + - swords + +# Do not exist in 1.18, that mean useInFuture will be set to true +# useInFuture set to true also mean it will not warn if there is an issue +restriction_swift_sneak: + useInFuture: true + enchantments: + - minecraft:swift_sneak + notAffectedGroups: + - enchanted_book + - leggings + +restriction_thorns: + enchantments: + - minecraft:thorns + notAffectedGroups: + - enchanted_book + - armors + +restriction__unbreaking: + enchantments: + - minecraft:unbreaking + notAffectedGroups: + - enchanted_book + - can_unbreak + +# ---------------------------------------------------- +# Now we have conflicts about enchantment Incompatibility +# We just filtered what item enchantments can be applied +# notAffectedGroups is empty as we don't want anything to not respect theses rules +# maxEnchantmentBeforeConflict is set to 1 to only have 1 on those enchantment available +# ---------------------------------------------------- + +sword_enchant_conflict: + enchantments: + - minecraft:bane_of_arthropods + - minecraft:smite + - minecraft:sharpness + - minecraft:density + - minecraft:breach + notAffectedGroups: [] + maxEnchantmentBeforeConflict: 1 + +protection_enchant_conflict: + enchantments: + - minecraft:blast_protection + - minecraft:fire_protection + - minecraft:projectile_protection + - minecraft:protection + notAffectedGroups: [] + maxEnchantmentBeforeConflict: 1 + +trident_conflict1: + enchantments: + - minecraft:channeling + - minecraft:riptide + notAffectedGroups: [] + maxEnchantmentBeforeConflict: 1 + +trident_conflict2: + enchantments: + - minecraft:loyalty + - minecraft:riptide + notAffectedGroups: [] + maxEnchantmentBeforeConflict: 1 + +boot_conflict: + enchantments: + - minecraft:depth_strider + - minecraft:frost_walker + notAffectedGroups: [] + maxEnchantmentBeforeConflict: 1 + +tool_conflict: + enchantments: + - minecraft:fortune + - minecraft:silk_touch + notAffectedGroups: [] + maxEnchantmentBeforeConflict: 1 + +bow_conflict: + enchantments: + - minecraft:mending + - minecraft:infinity + notAffectedGroups: [] + maxEnchantmentBeforeConflict: 1 + +crossbow_conflict: + enchantments: + - minecraft:multishot + - minecraft:piercing + notAffectedGroups: [] + maxEnchantmentBeforeConflict: 1 +restriction_density: + enchantments: + - minecraft:density + notAffectedGroups: + - mace + - enchanted_book +restriction_breach: + enchantments: + - minecraft:breach + notAffectedGroups: + - mace + - enchanted_book +restriction_wind_burst: + enchantments: + - minecraft:wind_burst + notAffectedGroups: + - mace + - enchanted_book +restriction_lunge: + enchantments: + - minecraft:lunge + notAffectedGroups: + - spears + - enchanted_book + +# ---------------------------------------------------- +# Bellow is for custom conflicts. +# This is also where conflict create from the gui will be placed. +# ---------------------------------------------------- diff --git a/defaultconfigs/1.21.11/item_groups.yml b/defaultconfigs/1.21.11/item_groups.yml new file mode 100644 index 0000000..9f2a877 --- /dev/null +++ b/defaultconfigs/1.21.11/item_groups.yml @@ -0,0 +1,247 @@ +# +# It is recommended that you use /configanvil to edit theses config. +# You can still manually edit here if you like to. but if you do, don't forget to /anvilconfigreload after you changes ! +# + +# Please note this config use spigot material names. +# It should match minecraft name in most case, maybe every case, but I can't be sure +# In case there an issue with material name, you can found them here: +# https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Material.html + +# An empty Exclude group exclude nothing, so it contain everything +everything: + type: exclude + +# An empty include group will include nothing +nothing: + type: include + +# This group is an example of a group including only stone and polished granite +example_include: + type: include + items: + - stone + - polished_granite + +# This group contain everything except polished granite and elements of example_include +example_exclude: + type: exclude + items: + - polished_granite + groups: + - example_include + +# Default configuration should be vanilla enchantment conflict group +# there may have error, if you find one you can fix it ! +# https://minecraft.fandom.com/wiki/Enchanting + +swords: + type: include + items: + - wooden_sword + - stone_sword + - iron_sword + - diamond_sword + - golden_sword + - netherite_sword + - copper_sword + +axes: + type: include + items: + - wooden_axe + - stone_axe + - iron_axe + - diamond_axe + - golden_axe + - netherite_axe + - copper_axe + +melee_weapons: + type: include + groups: + - swords + - axes + - spears + +helmets: + type: include + items: + - leather_helmet + - chainmail_helmet + - iron_helmet + - diamond_helmet + - golden_helmet + - netherite_helmet + - turtle_helmet + - copper_helmet + +chestplate: + type: include + items: + - leather_chestplate + - chainmail_chestplate + - iron_chestplate + - diamond_chestplate + - golden_chestplate + - netherite_chestplate + - copper_chestplate + +leggings: + type: include + items: + - leather_leggings + - chainmail_leggings + - iron_leggings + - diamond_leggings + - golden_leggings + - netherite_leggings + - copper_leggings + +boots: + type: include + items: + - leather_boots + - chainmail_boots + - iron_boots + - diamond_boots + - golden_boots + - netherite_boots + - copper_boots + +armors: + type: include + groups: + - helmets + - chestplate + - leggings + - boots + +wearable: + type: include + items: + - elytra + - carved_pumpkin + - skeleton_skull + - wither_skeleton_skull + - zombie_head + - player_head + - creeper_head + - dragon_head + - piglin_head + groups: + - armors + +pickaxes: + type: include + items: + - wooden_pickaxe + - stone_pickaxe + - iron_pickaxe + - diamond_pickaxe + - golden_pickaxe + - netherite_pickaxe + - copper_pickaxe + +shovels: + type: include + items: + - wooden_shovel + - stone_shovel + - iron_shovel + - diamond_shovel + - golden_shovel + - netherite_shovel + - copper_shovel + +hoes: + type: include + items: + - wooden_hoe + - stone_hoe + - iron_hoe + - diamond_hoe + - golden_hoe + - netherite_hoe + - copper_hoe + +tools: + type: include + groups: + - pickaxes + - shovels + - hoes + - axes + +enchanted_book: + type: include + items: + - enchanted_book + +trident: + type: include + items: + - trident + +bow: + type: include + items: + - bow + +crossbow: + type: include + items: + - crossbow + +fishing_rod: + type: include + items: + - fishing_rod + +shears: + type: include + items: + - shears + +can_unbreak: + type: include + items: + - elytra + - flint_and_steel + - shield + - carrot_on_a_stick + - warped_fungus_on_a_stick + - brush + groups: + - melee_weapons + - tools + - armors + - trident + - bow + - crossbow + - fishing_rod + - shears + - mace + +can_vanish: + type: include + items: + - compass + groups: + - wearable + - can_unbreak +mace: + type: include + items: + - mace +spears: + type: include + items: + - wooden_spear + - golden_spear + - stone_spear + - copper_spear + - iron_spear + - diamond_spear + - netherite_spear + diff --git a/defaultconfigs/1.21.11/unit_repair_item.yml b/defaultconfigs/1.21.11/unit_repair_item.yml new file mode 100644 index 0000000..ed981b1 --- /dev/null +++ b/defaultconfigs/1.21.11/unit_repair_item.yml @@ -0,0 +1,220 @@ +# +# It is recommended that you use /configanvil to edit theses config. +# You can still manually edit here if you like to. but if you do, don't forget to /anvilconfigreload after you changes ! +# + +# Unit repair configuration +# +# This configuration is to make custom unit repair +# A unit repair is, for example, a diamond to repair a diamond sword +# In vanilla, a unit repair 25% of object durability +# you can make a custom value here +# +# Item name should NOT combine caps and no caps (example: Stone) + +# Default value if the config is an invalid value (value <= 0 ) +# If value > 1 it will be treated as being = 1 +default_repair_amount: 0.25 + +# You can add custom unit repair +# The example bellow make a shield repaired by 10% by sticks + +# stick: +# shield: 0.10 + + +# Vanilla unit repair group is bellow +diamond: + diamond_helmet: 0.25 + diamond_chestplate: 0.25 + diamond_leggings: 0.25 + diamond_boots: 0.25 + diamond_sword: 0.25 + diamond_pickaxe: 0.25 + diamond_axe: 0.25 + diamond_shovel: 0.25 + diamond_hoe: 0.25 + diamond_spear: 0.25 + +netherite_ingot: + netherite_helmet: 0.25 + netherite_chestplate: 0.25 + netherite_leggings: 0.25 + netherite_boots: 0.25 + netherite_sword: 0.25 + netherite_pickaxe: 0.25 + netherite_axe: 0.25 + netherite_shovel: 0.25 + netherite_hoe: 0.25 + netherite_spear: 0.25 + +gold_ingot: + golden_helmet: 0.25 + golden_chestplate: 0.25 + golden_leggings: 0.25 + golden_boots: 0.25 + golden_sword: 0.25 + golden_pickaxe: 0.25 + golden_axe: 0.25 + golden_shovel: 0.25 + golden_hoe: 0.25 + golden_spear: 0.25 + +iron_ingot: + iron_helmet: 0.25 + iron_chestplate: 0.25 + iron_leggings: 0.25 + iron_boots: 0.25 + iron_sword: 0.25 + iron_pickaxe: 0.25 + iron_axe: 0.25 + iron_shovel: 0.25 + iron_hoe: 0.25 + iron_spear: 0.25 + +cobblestone: + stone_sword: 0.25 + stone_pickaxe: 0.25 + stone_axe: 0.25 + stone_shovel: 0.25 + stone_hoe: 0.25 + stone_spear: 0.25 + +cobbled_deepslate: + stone_sword: 0.25 + stone_pickaxe: 0.25 + stone_axe: 0.25 + stone_shovel: 0.25 + stone_hoe: 0.25 + stone_spear: 0.25 + +blackstone: + stone_sword: 0.25 + stone_pickaxe: 0.25 + stone_axe: 0.25 + stone_shovel: 0.25 + stone_hoe: 0.25 + +leather: + leather_helmet: 0.25 + leather_chestplate: 0.25 + leather_leggings: 0.25 + leather_boots: 0.25 + +phantom_membrane: + elytra: 0.25 + +scute: + turtle_helmet: 0.25 + +oak_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + wooden_spear: 0.25 + +spruce_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + wooden_spear: 0.25 + +birch_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + wooden_spear: 0.25 + +jungle_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + wooden_spear: 0.25 + +acacia_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + wooden_spear: 0.25 + +dark_oak_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + wooden_spear: 0.25 + +mangrove_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + wooden_spear: 0.25 + +cherry_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + wooden_spear: 0.25 + +bamboo_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + wooden_spear: 0.25 + +crimson_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + wooden_spear: 0.25 + +warped_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + wooden_spear: 0.25 +breeze_rod: + mace: 0.25 +copper_ingot: + copper_helmet: 0.25 + copper_chestplate: 0.25 + copper_leggings: 0.25 + copper_boots: 0.25 + copper_pickaxe: 0.25 + copper_shovel: 0.25 + copper_hoe: 0.25 + copper_axe: 0.25 + copper_sword: 0.25 + copper_spear: 0.25 diff --git a/defaultconfigs/1.21.9/README.md b/defaultconfigs/1.21.9/README.md new file mode 100644 index 0000000..2b3bfa8 --- /dev/null +++ b/defaultconfigs/1.21.9/README.md @@ -0,0 +1,6 @@ +### Default Plugin's Configurations For 1.21.9 +- [config.yml](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21.9/config.yml) +- [enchant_conflict.yml](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21.9/enchant_conflict.yml) +- [item_groups.yml](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21.9/item_groups.yml) +- [unit_repair_item.yml](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21.9/unit_repair_item.yml) +- [custom_recipes.yml](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21.9/custom_recipes.yml) diff --git a/defaultconfigs/1.21.9/config.yml b/defaultconfigs/1.21.9/config.yml new file mode 100644 index 0000000..4df5876 --- /dev/null +++ b/defaultconfigs/1.21.9/config.yml @@ -0,0 +1,479 @@ +# +# It is recommended that you use /configanvil to edit theses config. +# You can still manually edit here if you like to. but if you do, don't forget to /anvilconfigreload after you changes ! +# + +# What service of metric should custom anvil use +# Custom anvil collect generic information like server minecraft version, type, etc... +# It can also collect error information if error is happening (currently faststats only) +# It can also be disabled +# Please refer to README for public metric link +# Possible options: auto, bstat, faststats, disabled (auto by default) +metric_type: auto + +# Allow to report errors made caused by this plugin (only for faststats) +# This allows me to fix potentials issue that I'm not aware of +# Accept true or false (true by default) +metric_collect_errors: true + +# All anvil cost will be capped to limit_repair_value if enabled. +# +# In other words: +# For any anvil cost greater than limit_repair_value, Cost will be set to limit_repair_value. +limit_repair_cost: false + +# Max cost value the Anvil can get to. +# +# Valid values include 0 to 1000. +# Cost will be displayed as "Too Expensive": +# - If Cost is above 39 +# - And replace_too_expensive is disabled (false) +limit_repair_value: 39 + +# Whether the anvil's cost limit should be removed entirely. +# +# The anvil will still visually display "Too Expensive" if "replace_too_expensive" is disabled +# However, the action will be completable if xp requirement is meet. +remove_repair_limit: false + +# Whenever anvil cost is above 39 should display the true price and not "Too Expensive". +# +# However, when bypassing "Too Expensive", anvil price will be displayed as Green. +# If the action is not completable, the cost will still be displayed as "Too expensive". +# That mean you also need to change other settings like remove_repair_limit or limit_repair_cost. +# +# Require ProtocoLib. +replace_too_expensive: false + +# XP Level amount added to the anvil when the item is repaired by another item of the same type +# +# Valid values include 0 to 1000 +item_repair_cost: 2 + +# XP Level amount added to the anvil when the item is renamed +# +# Valid values include 0 to 1000 +item_rename_cost: 1 + +# XP Level amount added to the anvil when the item is repaired by an "unit" +# For example: a Diamond on a Diamond Sword +# What's considered unit for what can be edited on the unit repair configuration. +# +# Valid values include 0 to 1000 +unit_repair_cost: 1 + +# XP Level amount added to the anvil when a sacrifice enchantment +# conflict with one of the left item enchantment +# +# Valid values include 0 to 1000 +sacrifice_illegal_enchant_cost: 1 + +# Allow using color code and hexadecimal color. +# +# Color code are prefixed by "&" and hexadecimal color by "#". +# Color code will not be applied if it colors nothing. "&&" can be used to write "&". +# For minimessage search for minimessage formating https://docs.papermc.io/adventure/minimessage/format/ +allow_color_code: false +allow_hexadecimal_color: false +allow_minimessage: false + +# This enables restricting color code for player having specific permission +# It requires allow_color_code enabled for... obvious reasons +# +# For example: if player want to use "&aHello" it will be required that the player has +# the permission "ca.color.code.a" as he used the color code "a" +# In general permission to give to the player is "ca.color.code.[code]" +# where [code] is the color code you wish to allow the player +# +# It is kinda of useless when minimessage is supported as players would be able to bypass +# that using the equivalent minimessage tag +per_color_code_permission: false + +# Toggle if color should only be applicable if the player a certain permission. +# +# permission are "ca.color.code" for use of color code and "ca.color.hex" for use of hexadecimal color. +permission_needed_for_color: true + +# Xp cost if the player use color in the items name on rename. +# +# Valid values include 0 to 1000. +use_of_color_cost: 0 + +# Dialogue rename menu make use of dialog menu to allow bigger rename +# You can also change the maximum size and set it to -1 or less for maximum +# +# This feature only work on paper 1.21.7 or later +# +# At the moment only english is available for this menu... sorry ! +# +# CustomAnvil use "ca.rename.dialog" when permission +enable_dialog_rename: false +dialog_rename_max_size: 256 +permission_needed_for_dialog_rename: false + +# This allows custom anvil to not "guess" the text used for rename but store it in the item +# It will make item stackable only and only if it had used the same rename text +# +# For practical reason. this only work when dialog rename is enabled +dialog_rename_keep_user_text: true + +# Override limits for specific enchants +# +# Enchantments not listed here will use the value of default_limit +# +# Overrides provided default from aqua_affinity to depth_strider won't change effect with extra levels +# +# Valid range of 0 - 255 for each enchantment +# -1 mean keep default +enchant_limits: + minecraft:aqua_affinity: 1 + minecraft:binding_curse: 1 + minecraft:channeling: 1 + minecraft:flame: 1 + minecraft:infinity: 1 + minecraft:mending: 1 + minecraft:multishot: 1 + minecraft:silk_touch: 1 + minecraft:vanishing_curse: 1 + minecraft:depth_strider: 3 # anything more than 3 is treated as 3 by the game + minecraft:protection: 4 + minecraft:fire_protection: 4 + minecraft:blast_protection: 4 + minecraft:projectile_protection: 4 + minecraft:feather_falling: 4 + minecraft:thorns: 3 + minecraft:respiration: 3 + minecraft:sharpness: 5 + minecraft:smite: 5 + minecraft:bane_of_arthropods: 5 + minecraft:knockback: 2 + minecraft:fire_aspect: 2 + minecraft:looting: 3 + minecraft:sweeping: 3 + minecraft:sweeping_edge: 3 + minecraft:efficiency: 5 + minecraft:unbreaking: 3 + minecraft:fortune: 3 + minecraft:power: 5 + minecraft:punch: 2 + minecraft:luck_of_the_sea: 3 + minecraft:lure: 3 + minecraft:frost_walker: 2 + minecraft:impaling: 5 + minecraft:riptide: 3 + minecraft:loyalty: 3 + minecraft:piercing: 4 + minecraft:quick_charge: 3 + minecraft:soul_speed: 3 + minecraft:swift_sneak: 3 + minecraft:density: 5 + minecraft:breach: 4 + minecraft:wind_burst: 3 + +# Multipliers used to calculate the enchantment's value in repair/combining +# +# Values here are pulled from the fandom wiki: +# https://minecraft.fandom.com/wiki/Anvil_mechanics#Costs_for_combining_enchantments +# +# If an enchantment is missing values here, or is less than 0, it will default to 0 +# +# Calculated as: [Enchantment lvl] * [multiplier] +# +# With default values protection 4 would have a value of 4 when +# coming from either a book (4 * 1) or an item (4 * 1) +enchant_values: + minecraft:aqua_affinity: + item: 4 + book: 2 + minecraft:bane_of_arthropods: + item: 2 + book: 1 + minecraft:binding_curse: + item: 8 + book: 4 + minecraft:blast_protection: + item: 4 + book: 2 + minecraft:channeling: + item: 8 + book: 4 + minecraft:depth_strider: + item: 4 + book: 2 + minecraft:efficiency: + item: 1 + book: 1 + minecraft:flame: + item: 4 + book: 2 + minecraft:feather_falling: + item: 2 + book: 1 + minecraft:fire_aspect: + item: 4 + book: 2 + minecraft:fire_protection: + item: 2 + book: 1 + minecraft:fortune: + item: 4 + book: 2 + minecraft:frost_walker: + item: 4 + book: 2 + minecraft:impaling: + item: 4 + book: 2 + minecraft:infinity: + item: 8 + book: 4 + minecraft:knockback: + item: 2 + book: 1 + minecraft:looting: + item: 4 + book: 2 + minecraft:loyalty: + item: 1 + book: 1 + minecraft:luck_of_the_sea: + item: 4 + book: 2 + minecraft:lure: + item: 4 + book: 2 + minecraft:mending: + item: 4 + book: 2 + minecraft:multishot: + item: 4 + book: 2 + minecraft:piercing: + item: 1 + book: 1 + minecraft:power: + item: 1 + book: 1 + minecraft:projectile_protection: + item: 2 + book: 1 + minecraft:protection: + item: 1 + book: 1 + minecraft:punch: + item: 4 + book: 2 + minecraft:quick_charge: + item: 2 + book: 1 + minecraft:respiration: + item: 4 + book: 2 + minecraft:riptide: + item: 4 + book: 2 + minecraft:silk_touch: + item: 8 + book: 4 + minecraft:sharpness: + item: 1 + book: 1 + minecraft:smite: + item: 2 + book: 1 + minecraft:soul_speed: + item: 8 + book: 4 + minecraft:swift_sneak: + item: 8 + book: 4 + minecraft:sweeping: + item: 4 + book: 2 + minecraft:sweeping_edge: + item: 4 + book: 2 + minecraft:thorns: + item: 8 + book: 4 + minecraft:unbreaking: + item: 2 + book: 1 + minecraft:vanishing_curse: + item: 8 + book: 4 + minecraft:density: + item: 2 + book: 1 + minecraft:breach: + item: 4 + book: 2 + minecraft:wind_burst: + item: 4 + book: 2 + +# Disable enchantment merging for level above the set value +# Enchantment merging is when, for example, 2 unbreaking II book combine to give sharpness III +# But Enchantment above this value can still be applied. following the previous example, we could still apply a unbreaking III book to a sword +# Even if disable-merge-over of unbreaking is set to 2 +# -1 mean enchantment merge for this enchantment is not disabled. default to -1 if absent. +disable-merge-over: + # Sharpness is set to -1. it equivalent to it not being set to anything (and work as vanilla on default configuration) + minecraft:sharpness: -1 +# If uncommented. 2 unbreaking II book would not give an unbreaking III book. but unbreaking III book can still be applied +# minecraft:unbreaking: 2 + +# The maximum number of enchantment an item can get. -1 for infinity +# Use eco enchant enchant_limit if present by default unless "default" is not equal to -1 +enchantment_count_limit: + default: -1 + # Limit for specific items. example bellow is an example with stick + # Per item enchantment limit override eco enchant enchant_limit and default limit + items: + stick: -1 + +# Settings for lore modification +lore_edit: + book_and_quil: + # Permission is ca.lore_edit.book + use_permission: true + append: + # If adding lore using book & quil is enabled + enabled: false + # Cost used every time + fixed_cost: 1 + # Cost used for every lore line added + per_line_cost: 0 + # Use left item vanilla cost penalty if any + shared_increase: false + # Increase shared left item cost penalty + shared_additive: false + # If adding the lore consume the book & quil + do_consume: false + # Allow using color code and hexadecimal color when editing lore via book & quil + # + # Color code are prefixed by "&" and hexadecimal color by "#" + # Color code will not be applied if it colors nothing. "&&" can be used to write "&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + # + # Note that currently minimessage would disable hex code when adding color + allow_color_code: true + allow_hexadecimal_color: false + allow_minimessage: true + + remove: + # If removing lore using book & quil is enabled + enabled: false + # Cost used every time + fixed_cost: 1 + # Cost used for every lore line removed + per_line_cost: 0 + # Use left item vanilla cost penalty if any + shared_increase: false + # Increase shared left item cost penalty + shared_additive: false + # If removing the lore consume the book & quil + do_consume: false + # Cost of replacing colors + remove_color_cost: 0 + # Allowed some color and tags to be reverted to plain text + # Custom anvil will prioritise format that result is a smaller resulting text + # Note that not allowing certain format will lead to some lost of color or tags. + # If configuration are exact as append appending this book should result in the exact same color + # + # Color code will be prefixed by "&" and hexadecimal color by "#". + # If color code is allowed, "&" in the text will get converted to "&&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + allow_color_code: true + allow_hexadecimal_color: false + allow_minimessage: true + + paper: + # Permission is ca.lore_edit.paper + use_permission: true + # what order should the lines should get added/removed (start/end, if invalid or not present will be end) + order: end + + append_line: + # If adding lore line using paper is enabled + enabled: false + # Cost used every time + fixed_cost: 1 + # Use left item vanilla cost penalty if any + shared_increase: false + # Increase shared left item cost penalty + shared_additive: false + # If adding the lore line consume the paper + do_consume: false + # Allow using color code and hexadecimal color when editing lore via book & quil + # + # Color code are prefixed by "&" and hexadecimal color by "#" + # Color code will not be applied if it colors nothing. "&&" can be used to write "&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + # + # Note that currently minimessage would disable hex code when adding color + allow_color_code: true + allow_hexadecimal_color: false + allow_minimessage: true + color_use_cost: 0 + + remove_line: + # If removing lore line using paper is enabled + enabled: false + # Cost used every time + fixed_cost: 1 + # Use left item vanilla cost penalty if any + shared_increase: false + # Increase shared left item cost penalty + shared_additive: false + # If removing the lore line consume the paper + do_consume: false + # Cost of replacing colors + remove_color_cost: 0 + # Allowed some color and tags to be reverted to plain text + # Custom anvil will prioritise format that result is a smaller resulting text + # Note that not allowing certain format will lead to some lost of color or tags. + # If configuration are exact as append appending this paper should result in the exact same color + # + # Color code will be prefixed by "&" and hexadecimal color by "#". + # If color code is allowed, "&" in the text will get converted to "&&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + allow_color_code: true + allow_hexadecimal_color: false + allow_minimessage: true + +# Allow to replace the xp cost by a monetary cost +# If enabled it will not be bound to the experience level limits +# +# It also requires to enable dialog rename (set "enable_dialog_rename: false" a bit higher) +# If dialog rename permission is enabled and player do not have the permission merge will fall back to vanilla xp cost +# +# If you are using custom craft I recommend using Linear Xp Cost with Exact Linear Xp as normal Xp Cost will act "weird" +# But Linear Xp will act as 1$ time global multiplier. In other word: like you expect +# +# As this feature require dialog rename, it can only be enabled starting with paper 1.21.6 and later +monetary_cost: + enabled: false + # If using vault unlocked this allow to specify what currency should be used for anvil usage + # default being the default currency + currency: default + # multiply the anvil cost by a value to allow to have price a big bigger than like 40 + multipliers: + # global multipliers. all usage type will be multiplied by this value + global: 1.0 + # usage specific type. it will only apply for specific xp "reason" + enchantment: 1.0 # related to enchantments level + repair: 1.0 # for repairing via unit repair (per unit) + rename: 1.0 # for renaming the item + lore_edit: 1.0 # for changing the lore of the item (only if lore edit is enabled) + work_penalty: 1.0 # for work penalty (aka use penalty) + recipe: 1.0 # for custom anvil recipe cost + +# Whether to show debug logging +debug_log: false + +# Whether to show verbose debug logging +debug_log_verbose: false + +configVersion: 1.11.0 +lowMinecraftVersion: 1.21.9 diff --git a/defaultconfigs/1.21.9/custom_recipes.yml b/defaultconfigs/1.21.9/custom_recipes.yml new file mode 100644 index 0000000..57c2220 --- /dev/null +++ b/defaultconfigs/1.21.9/custom_recipes.yml @@ -0,0 +1,5 @@ +# ---------------------------------------------------- +# This config file is to store custom craft +# It is recommended to use the in game config editor for this configuration. +# /customanvilconfig With ca.config.edit permission +# ---------------------------------------------------- diff --git a/defaultconfigs/1.21.9/enchant_conflict.yml b/defaultconfigs/1.21.9/enchant_conflict.yml new file mode 100644 index 0000000..9205061 --- /dev/null +++ b/defaultconfigs/1.21.9/enchant_conflict.yml @@ -0,0 +1,389 @@ +# +# It is recommended that you use /configanvil to edit theses config. +# You can still manually edit here if you like to. but if you do, don't forget to /anvilconfigreload after you changes ! +# + +# material conflicts +# +# If you want to edit this file: +# - A conflict will apply to every item except if in one of the notAffectedGroups group +# - the conflict will count only if the user try to combine at least as +# many conflicting enchantment as "maxEnchantmentBeforeConflict" +# +# +# ---------------------------------------------------- +# These restriction are about not allowing enchantment +# on illegal items +# ---------------------------------------------------- + +restriction_aqua_affinity: + enchantments: + - minecraft:aqua_affinity + notAffectedGroups: + - enchanted_book + - helmets + +restriction_bane_of_arthropods: + enchantments: + - minecraft:bane_of_arthropods + notAffectedGroups: + - enchanted_book + - melee_weapons + - mace + +restriction_blast_protection: + enchantments: + - minecraft:blast_protection + notAffectedGroups: + - enchanted_book + - armors + +restriction_channeling: + enchantments: + - minecraft:channeling + notAffectedGroups: + - enchanted_book + - trident + +restriction_binding_curse: + enchantments: + - minecraft:binding_curse + notAffectedGroups: + - enchanted_book + - wearable + +restriction_vanishing_curse: + enchantments: + - minecraft:vanishing_curse + notAffectedGroups: + - enchanted_book + - can_vanish + +restriction_depth_strider: + enchantments: + - minecraft:depth_strider + notAffectedGroups: + - enchanted_book + - boots + +restriction_efficiency: + enchantments: + - minecraft:efficiency + notAffectedGroups: + - enchanted_book + - tools + - shears + +restriction_feather_falling: + enchantments: + - minecraft:feather_falling + notAffectedGroups: + - enchanted_book + - boots + +restriction_fire_aspect: + enchantments: + - minecraft:fire_aspect + notAffectedGroups: + - enchanted_book + - swords + - mace + +restriction_fire_protection: + enchantments: + - minecraft:fire_protection + notAffectedGroups: + - enchanted_book + - armors + +restriction_flame: + enchantments: + - minecraft:flame + notAffectedGroups: + - enchanted_book + - bow + +restriction_fortune: + enchantments: + - minecraft:fortune + notAffectedGroups: + - enchanted_book + - tools + +restriction_frost_walker: + enchantments: + - minecraft:frost_walker + notAffectedGroups: + - enchanted_book + - boots + +restriction_impaling: + enchantments: + - minecraft:impaling + notAffectedGroups: + - enchanted_book + - trident + +restriction_infinity: + enchantments: + - minecraft:infinity + notAffectedGroups: + - enchanted_book + - bow + +restriction_knockback: + enchantments: + - minecraft:knockback + notAffectedGroups: + - enchanted_book + - swords + +restriction_looting: + enchantments: + - minecraft:looting + notAffectedGroups: + - enchanted_book + - swords + +restriction_loyalty: + enchantments: + - minecraft:loyalty + notAffectedGroups: + - enchanted_book + - trident + +restriction_luck_of_the_sea: + enchantments: + - minecraft:luck_of_the_sea + notAffectedGroups: + - enchanted_book + - fishing_rod + +restriction_lure: + enchantments: + - minecraft:lure + notAffectedGroups: + - enchanted_book + - fishing_rod + +restriction_mending: + enchantments: + - minecraft:mending + notAffectedGroups: + - enchanted_book + - can_unbreak + +restriction_minecraft_multishot: + enchantments: + - minecraft:multishot + notAffectedGroups: + - enchanted_book + - crossbow + +restriction_piercing: + enchantments: + - minecraft:piercing + notAffectedGroups: + - enchanted_book + - crossbow + +restriction_power: + enchantments: + - minecraft:power + notAffectedGroups: + - enchanted_book + - bow + +restriction_projectile_protection: + enchantments: + - minecraft:projectile_protection + notAffectedGroups: + - enchanted_book + - armors + +restriction_protection: + enchantments: + - minecraft:protection + notAffectedGroups: + - enchanted_book + - armors + +restriction_punch: + enchantments: + - minecraft:punch + notAffectedGroups: + - enchanted_book + - bow + +restriction_quick_charge: + enchantments: + - minecraft:quick_charge + notAffectedGroups: + - enchanted_book + - crossbow + +restriction_respiration: + enchantments: + - minecraft:respiration + notAffectedGroups: + - enchanted_book + - helmets + +restriction_riptide: + enchantments: + - minecraft:riptide + notAffectedGroups: + - enchanted_book + - trident + +restriction_sharpness: + enchantments: + - minecraft:sharpness + notAffectedGroups: + - enchanted_book + - melee_weapons + +restriction__silk_touch: + enchantments: + - minecraft:silk_touch + notAffectedGroups: + - enchanted_book + - tools + +restriction_smite: + enchantments: + - minecraft:smite + notAffectedGroups: + - enchanted_book + - melee_weapons + - mace + +restriction_soul_speed: + enchantments: + - minecraft:soul_speed + notAffectedGroups: + - enchanted_book + - boots + +restriction_sweeping_edge: + enchantments: + - minecraft:sweeping + - minecraft:sweeping_edge + notAffectedGroups: + - enchanted_book + - swords + +# Do not exist in 1.18, that mean useInFuture will be set to true +# useInFuture set to true also mean it will not warn if there is an issue +restriction_swift_sneak: + useInFuture: true + enchantments: + - minecraft:swift_sneak + notAffectedGroups: + - enchanted_book + - leggings + +restriction_thorns: + enchantments: + - minecraft:thorns + notAffectedGroups: + - enchanted_book + - armors + +restriction__unbreaking: + enchantments: + - minecraft:unbreaking + notAffectedGroups: + - enchanted_book + - can_unbreak + +# ---------------------------------------------------- +# Now we have conflicts about enchantment Incompatibility +# We just filtered what item enchantments can be applied +# notAffectedGroups is empty as we don't want anything to not respect theses rules +# maxEnchantmentBeforeConflict is set to 1 to only have 1 on those enchantment available +# ---------------------------------------------------- + +sword_enchant_conflict: + enchantments: + - minecraft:bane_of_arthropods + - minecraft:smite + - minecraft:sharpness + - minecraft:density + - minecraft:breach + notAffectedGroups: [] + maxEnchantmentBeforeConflict: 1 + +protection_enchant_conflict: + enchantments: + - minecraft:blast_protection + - minecraft:fire_protection + - minecraft:projectile_protection + - minecraft:protection + notAffectedGroups: [] + maxEnchantmentBeforeConflict: 1 + +trident_conflict1: + enchantments: + - minecraft:channeling + - minecraft:riptide + notAffectedGroups: [] + maxEnchantmentBeforeConflict: 1 + +trident_conflict2: + enchantments: + - minecraft:loyalty + - minecraft:riptide + notAffectedGroups: [] + maxEnchantmentBeforeConflict: 1 + +boot_conflict: + enchantments: + - minecraft:depth_strider + - minecraft:frost_walker + notAffectedGroups: [] + maxEnchantmentBeforeConflict: 1 + +tool_conflict: + enchantments: + - minecraft:fortune + - minecraft:silk_touch + notAffectedGroups: [] + maxEnchantmentBeforeConflict: 1 + +bow_conflict: + enchantments: + - minecraft:mending + - minecraft:infinity + notAffectedGroups: [] + maxEnchantmentBeforeConflict: 1 + +crossbow_conflict: + enchantments: + - minecraft:multishot + - minecraft:piercing + notAffectedGroups: [] + maxEnchantmentBeforeConflict: 1 +restriction_density: + enchantments: + - minecraft:density + notAffectedGroups: + - mace + - enchanted_book +restriction_breach: + enchantments: + - minecraft:breach + notAffectedGroups: + - mace + - enchanted_book +restriction_wind_burst: + enchantments: + - minecraft:wind_burst + notAffectedGroups: + - mace + - enchanted_book + +# ---------------------------------------------------- +# Bellow is for custom conflicts. +# This is also where conflict create from the gui will be placed. +# ---------------------------------------------------- diff --git a/defaultconfigs/1.21.9/item_groups.yml b/defaultconfigs/1.21.9/item_groups.yml new file mode 100644 index 0000000..2dbd5db --- /dev/null +++ b/defaultconfigs/1.21.9/item_groups.yml @@ -0,0 +1,236 @@ +# +# It is recommended that you use /configanvil to edit theses config. +# You can still manually edit here if you like to. but if you do, don't forget to /anvilconfigreload after you changes ! +# + +# Please note this config use spigot material names. +# It should match minecraft name in most case, maybe every case, but I can't be sure +# In case there an issue with material name, you can found them here: +# https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Material.html + +# An empty Exclude group exclude nothing, so it contain everything +everything: + type: exclude + +# An empty include group will include nothing +nothing: + type: include + +# This group is an example of a group including only stone and polished granite +example_include: + type: include + items: + - stone + - polished_granite + +# This group contain everything except polished granite and elements of example_include +example_exclude: + type: exclude + items: + - polished_granite + groups: + - example_include + +# Default configuration should be vanilla enchantment conflict group +# there may have error, if you find one you can fix it ! +# https://minecraft.fandom.com/wiki/Enchanting + +swords: + type: include + items: + - wooden_sword + - stone_sword + - iron_sword + - diamond_sword + - golden_sword + - netherite_sword + - copper_sword + +axes: + type: include + items: + - wooden_axe + - stone_axe + - iron_axe + - diamond_axe + - golden_axe + - netherite_axe + - copper_axe + +melee_weapons: + type: include + groups: + - swords + - axes + +helmets: + type: include + items: + - leather_helmet + - chainmail_helmet + - iron_helmet + - diamond_helmet + - golden_helmet + - netherite_helmet + - turtle_helmet + - copper_helmet + +chestplate: + type: include + items: + - leather_chestplate + - chainmail_chestplate + - iron_chestplate + - diamond_chestplate + - golden_chestplate + - netherite_chestplate + - copper_chestplate + +leggings: + type: include + items: + - leather_leggings + - chainmail_leggings + - iron_leggings + - diamond_leggings + - golden_leggings + - netherite_leggings + - copper_leggings + +boots: + type: include + items: + - leather_boots + - chainmail_boots + - iron_boots + - diamond_boots + - golden_boots + - netherite_boots + - copper_boots + +armors: + type: include + groups: + - helmets + - chestplate + - leggings + - boots + +wearable: + type: include + items: + - elytra + - carved_pumpkin + - skeleton_skull + - wither_skeleton_skull + - zombie_head + - player_head + - creeper_head + - dragon_head + - piglin_head + groups: + - armors + +pickaxes: + type: include + items: + - wooden_pickaxe + - stone_pickaxe + - iron_pickaxe + - diamond_pickaxe + - golden_pickaxe + - netherite_pickaxe + - copper_pickaxe + +shovels: + type: include + items: + - wooden_shovel + - stone_shovel + - iron_shovel + - diamond_shovel + - golden_shovel + - netherite_shovel + - copper_shovel + +hoes: + type: include + items: + - wooden_hoe + - stone_hoe + - iron_hoe + - diamond_hoe + - golden_hoe + - netherite_hoe + - copper_hoe + +tools: + type: include + groups: + - pickaxes + - shovels + - hoes + - axes + +enchanted_book: + type: include + items: + - enchanted_book + +trident: + type: include + items: + - trident + +bow: + type: include + items: + - bow + +crossbow: + type: include + items: + - crossbow + +fishing_rod: + type: include + items: + - fishing_rod + +shears: + type: include + items: + - shears + +can_unbreak: + type: include + items: + - elytra + - flint_and_steel + - shield + - carrot_on_a_stick + - warped_fungus_on_a_stick + - brush + groups: + - melee_weapons + - tools + - armors + - trident + - bow + - crossbow + - fishing_rod + - shears + - mace + +can_vanish: + type: include + items: + - compass + groups: + - wearable + - can_unbreak +mace: + type: include + items: + - mace + diff --git a/defaultconfigs/1.21.9/unit_repair_item.yml b/defaultconfigs/1.21.9/unit_repair_item.yml new file mode 100644 index 0000000..0ce2bce --- /dev/null +++ b/defaultconfigs/1.21.9/unit_repair_item.yml @@ -0,0 +1,192 @@ +# +# It is recommended that you use /configanvil to edit theses config. +# You can still manually edit here if you like to. but if you do, don't forget to /anvilconfigreload after you changes ! +# + +# Unit repair configuration +# +# This configuration is to make custom unit repair +# A unit repair is, for example, a diamond to repair a diamond sword +# In vanilla, a unit repair 25% of object durability +# you can make a custom value here +# +# Item name should NOT combine caps and no caps (example: Stone) + +# Default value if the config is an invalid value (value <= 0 ) +# If value > 1 it will be treated as being = 1 +default_repair_amount: 0.25 + +# You can add custom unit repair +# The example bellow make a shield repaired by 10% by sticks + +# stick: +# shield: 0.10 + + +# Vanilla unit repair group is bellow +diamond: + diamond_helmet: 0.25 + diamond_chestplate: 0.25 + diamond_leggings: 0.25 + diamond_boots: 0.25 + diamond_sword: 0.25 + diamond_pickaxe: 0.25 + diamond_axe: 0.25 + diamond_shovel: 0.25 + diamond_hoe: 0.25 + +netherite_ingot: + netherite_helmet: 0.25 + netherite_chestplate: 0.25 + netherite_leggings: 0.25 + netherite_boots: 0.25 + netherite_sword: 0.25 + netherite_pickaxe: 0.25 + netherite_axe: 0.25 + netherite_shovel: 0.25 + netherite_hoe: 0.25 + +gold_ingot: + golden_helmet: 0.25 + golden_chestplate: 0.25 + golden_leggings: 0.25 + golden_boots: 0.25 + golden_sword: 0.25 + golden_pickaxe: 0.25 + golden_axe: 0.25 + golden_shovel: 0.25 + golden_hoe: 0.25 + +iron_ingot: + iron_helmet: 0.25 + iron_chestplate: 0.25 + iron_leggings: 0.25 + iron_boots: 0.25 + iron_sword: 0.25 + iron_pickaxe: 0.25 + iron_axe: 0.25 + iron_shovel: 0.25 + iron_hoe: 0.25 + +cobblestone: + stone_sword: 0.25 + stone_pickaxe: 0.25 + stone_axe: 0.25 + stone_shovel: 0.25 + stone_hoe: 0.25 + +cobbled_deepslate: + stone_sword: 0.25 + stone_pickaxe: 0.25 + stone_axe: 0.25 + stone_shovel: 0.25 + stone_hoe: 0.25 + +blackstone: + stone_sword: 0.25 + stone_pickaxe: 0.25 + stone_axe: 0.25 + stone_shovel: 0.25 + stone_hoe: 0.25 + +leather: + leather_helmet: 0.25 + leather_chestplate: 0.25 + leather_leggings: 0.25 + leather_boots: 0.25 + +phantom_membrane: + elytra: 0.25 + +scute: + turtle_helmet: 0.25 + +oak_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + +spruce_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + +birch_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + +jungle_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + +acacia_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + +dark_oak_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + +mangrove_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + +cherry_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + +bamboo_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + +crimson_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + +warped_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 +breeze_rod: + mace: 0.25 diff --git a/defaultconfigs/1.21/config.yml b/defaultconfigs/1.21/config.yml index 9c1ad67..5d59e5a 100644 --- a/defaultconfigs/1.21/config.yml +++ b/defaultconfigs/1.21/config.yml @@ -3,6 +3,19 @@ # You can still manually edit here if you like to. but if you do, don't forget to /anvilconfigreload after you changes ! # +# What service of metric should custom anvil use +# Custom anvil collect generic information like server minecraft version, type, etc... +# It can also collect error information if error is happening (currently faststats only) +# It can also be disabled +# Please refer to README for public metric link +# Possible options: auto, bstat, faststats, disabled (auto by default) +metric_type: auto + +# Allow to report errors made caused by this plugin (only for faststats) +# This allows me to fix potentials issue that I'm not aware of +# Accept true or false (true by default) +metric_collect_errors: true + # All anvil cost will be capped to limit_repair_value if enabled. # # In other words: @@ -59,8 +72,22 @@ sacrifice_illegal_enchant_cost: 1 # # Color code are prefixed by "&" and hexadecimal color by "#". # Color code will not be applied if it colors nothing. "&&" can be used to write "&". +# For minimessage search for minimessage formating https://docs.papermc.io/adventure/minimessage/format/ allow_color_code: false allow_hexadecimal_color: false +allow_minimessage: false + +# This enables restricting color code for player having specific permission +# It requires allow_color_code enabled for... obvious reasons +# +# For example: if player want to use "&aHello" it will be required that the player has +# the permission "ca.color.code.a" as he used the color code "a" +# In general permission to give to the player is "ca.color.code.[code]" +# where [code] is the color code you wish to allow the player +# +# It is kinda of useless when minimessage is supported as players would be able to bypass +# that using the equivalent minimessage tag +per_color_code_permission: false # Toggle if color should only be applicable if the player a certain permission. # @@ -72,10 +99,23 @@ permission_needed_for_color: true # Valid values include 0 to 1000. use_of_color_cost: 0 -# Default limit to apply to any enchants missing from enchant_limits +# Dialogue rename menu make use of dialog menu to allow bigger rename +# You can also change the maximum size and set it to -1 or less for maximum # -# Valid values include 1 to 1000 -default_limit: 5 +# This feature only work on paper 1.21.7 or later +# +# At the moment only english is available for this menu... sorry ! +# +# CustomAnvil use "ca.rename.dialog" when permission +enable_dialog_rename: false +dialog_rename_max_size: 256 +permission_needed_for_dialog_rename: false + +# This allows custom anvil to not "guess" the text used for rename but store it in the item +# It will make item stackable only and only if it had used the same rename text +# +# For practical reason. this only work when dialog rename is enabled +dialog_rename_keep_user_text: true # Override limits for specific enchants # @@ -83,7 +123,8 @@ default_limit: 5 # # Overrides provided default from aqua_affinity to depth_strider won't change effect with extra levels # -# Valid range of 1 - 255 for each enchantment +# Valid range of 0 - 255 for each enchantment +# -1 mean keep default enchant_limits: minecraft:aqua_affinity: 1 minecraft:binding_curse: 1 @@ -265,10 +306,19 @@ enchant_values: # Even if disable-merge-over of unbreaking is set to 2 # -1 mean enchantment merge for this enchantment is not disabled. default to -1 if absent. disable-merge-over: - # Sharpness is set to -1. it equivalent to it not being set to anything (and work as vanilla) + # Sharpness is set to -1. it equivalent to it not being set to anything (and work as vanilla on default configuration) minecraft:sharpness: -1 - # If uncommented. 2 unbreaking II book would not give an unbreaking III book. but unbreaking III book can still be applied - #minecraft:unbreaking: 2 +# If uncommented. 2 unbreaking II book would not give an unbreaking III book. but unbreaking III book can still be applied +# minecraft:unbreaking: 2 + +# The maximum number of enchantment an item can get. -1 for infinity +# Use eco enchant enchant_limit if present by default unless "default" is not equal to -1 +enchantment_count_limit: + default: -1 + # Limit for specific items. example bellow is an example with stick + # Per item enchantment limit override eco enchant enchant_limit and default limit + items: + stick: -1 # Settings for lore modification lore_edit: @@ -292,9 +342,12 @@ lore_edit: # # Color code are prefixed by "&" and hexadecimal color by "#" # Color code will not be applied if it colors nothing. "&&" can be used to write "&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + # + # Note that currently minimessage would disable hex code when adding color allow_color_code: true - allow_hexadecimal_color: true - use_cost: 0 + allow_hexadecimal_color: false + allow_minimessage: true remove: # If removing lore using book & quil is enabled @@ -309,16 +362,25 @@ lore_edit: shared_additive: false # If removing the lore consume the book & quil do_consume: false - # If the color should get back to color code or hex format - remove_color_on_remove: true # Cost of replacing colors remove_color_cost: 0 + # Allowed some color and tags to be reverted to plain text + # Custom anvil will prioritise format that result is a smaller resulting text + # Note that not allowing certain format will lead to some lost of color or tags. + # If configuration are exact as append appending this book should result in the exact same color + # + # Color code will be prefixed by "&" and hexadecimal color by "#". + # If color code is allowed, "&" in the text will get converted to "&&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + allow_color_code: true + allow_hexadecimal_color: false + allow_minimessage: true paper: # Permission is ca.lore_edit.paper use_permission: true # what order should the lines should get added/removed (start/end, if invalid or not present will be end) - order: "end" + order: end append_line: # If adding lore line using paper is enabled @@ -335,8 +397,12 @@ lore_edit: # # Color code are prefixed by "&" and hexadecimal color by "#" # Color code will not be applied if it colors nothing. "&&" can be used to write "&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + # + # Note that currently minimessage would disable hex code when adding color allow_color_code: true - allow_hexadecimal_color: true + allow_hexadecimal_color: false + allow_minimessage: true color_use_cost: 0 remove_line: @@ -350,10 +416,46 @@ lore_edit: shared_additive: false # If removing the lore line consume the paper do_consume: false - # If the color should get back to color code or hex format - remove_color_on_remove: true # Cost of replacing colors remove_color_cost: 0 + # Allowed some color and tags to be reverted to plain text + # Custom anvil will prioritise format that result is a smaller resulting text + # Note that not allowing certain format will lead to some lost of color or tags. + # If configuration are exact as append appending this paper should result in the exact same color + # + # Color code will be prefixed by "&" and hexadecimal color by "#". + # If color code is allowed, "&" in the text will get converted to "&&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + allow_color_code: true + allow_hexadecimal_color: false + allow_minimessage: true + +# Allow to replace the xp cost by a monetary cost +# If enabled it will not be bound to the experience level limits +# +# It also requires to enable dialog rename (set "enable_dialog_rename: false" a bit higher) +# If dialog rename permission is enabled and player do not have the permission merge will fall back to vanilla xp cost +# +# If you are using custom craft I recommend using Linear Xp Cost with Exact Linear Xp as normal Xp Cost will act "weird" +# But Linear Xp will act as 1$ time global multiplier. In other word: like you expect +# +# As this feature require dialog rename, it can only be enabled starting with paper 1.21.6 and later +monetary_cost: + enabled: false + # If using vault unlocked this allow to specify what currency should be used for anvil usage + # default being the default currency + currency: default + # multiply the anvil cost by a value to allow to have price a big bigger than like 40 + multipliers: + # global multipliers. all usage type will be multiplied by this value + global: 1.0 + # usage specific type. it will only apply for specific xp "reason" + enchantment: 1.0 # related to enchantments level + repair: 1.0 # for repairing via unit repair (per unit) + rename: 1.0 # for renaming the item + lore_edit: 1.0 # for changing the lore of the item (only if lore edit is enabled) + work_penalty: 1.0 # for work penalty (aka use penalty) + recipe: 1.0 # for custom anvil recipe cost # Whether to show debug logging debug_log: false @@ -361,10 +463,4 @@ debug_log: false # Whether to show verbose debug logging debug_log_verbose: false -# In case something when wrong with CustomAnvil packet manager. -# If you see "missing class exception" or similar you may test this. -# If enabled and Protocolib absent or disabled "Replace to expensive" will not work. -# ProtocoLib may also be used if the server is in an "unsupported" version even if this option is disabled. -force_protocolib: false - -configVersion: 1.8.0 +configVersion: 1.11.0 \ No newline at end of file diff --git a/defaultconfigs/1.21/enchant_conflict.yml b/defaultconfigs/1.21/enchant_conflict.yml index 0e8b3f3..45d62c3 100644 --- a/defaultconfigs/1.21/enchant_conflict.yml +++ b/defaultconfigs/1.21/enchant_conflict.yml @@ -92,6 +92,10 @@ restriction_loyalty: enchantments: [ minecraft:loyalty ] notAffectedGroups: [ enchanted_book, trident ] +restriction_luck_of_the_sea: + enchantments: [ minecraft:luck_of_the_sea ] + notAffectedGroups: [ enchanted_book, fishing_rod ] + restriction_lure: enchantments: [ minecraft:lure ] notAffectedGroups: [ enchanted_book, fishing_rod ] diff --git a/defaultconfigs/README.md b/defaultconfigs/README.md index 72c88ca..daa2088 100644 --- a/defaultconfigs/README.md +++ b/defaultconfigs/README.md @@ -1,3 +1,5 @@ ### Default Plugin's Configurations -For 1.18 to 1.20.6 use [1.18 configurations](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.18) \ -For 1.21 use [1.21 configurations](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21) \ No newline at end of file +From 1.18 to 1.20.6 use [1.18 configurations](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.18) \ +From 1.21 to 1.21.8 use [1.21 configurations](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21) \ +From 1.21.9 to 1.21.10 use [1.21.9 configurations](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21.9) \ +From 1.21.11 use [1.21.11 configurations](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21.11) diff --git a/gradle.properties b/gradle.properties index 420f8a0..4397d2e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,8 +1,13 @@ kotlin.code.style=official # Signing -signing.gnupg.executable=gpg -signing.gnupg.useLegacyGpg=true -signing.gnupg.keyName=B0DBF91F +signing.secretKeyRingFile=~/.gnupg/secring.gpg + +kotlin.daemon.jvmargs=-Xmx8G + +# list of nms +subprojects.reobfnms=v1_17R1,v1_18R1,v1_18R2,v1_19R1,v1_19R2,v1_19R3,v1_20R1,v1_20R2,v1_20R3,v1_20R4,v1_21R1,v1_21R2,v1_21R3,v1_21R4,v1_21R5,v1_21R6,v1_21R7 + +# list of version for hangar release +paperVersion=1.18-26.2 -kotlin.daemon.jvmargs=-Xmx8G \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d6e308a..0b55a3b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/impl/ExcellentEnchant5_4/build.gradle.kts b/impl/ExcellentEnchant5_4/build.gradle.kts new file mode 100644 index 0000000..9fe598b --- /dev/null +++ b/impl/ExcellentEnchant5_4/build.gradle.kts @@ -0,0 +1,17 @@ +group = rootProject.group +version = rootProject.version + +plugins { + kotlin("jvm") version "2.3.0" +} + +repositories { + // ExcellentEnchants + maven(url = "https://repo.nightexpressdev.com/releases") +} + +dependencies { + // Excellent Enchant + compileOnly("su.nightexpress.excellentenchants:Core:5.4.3") + compileOnly("su.nightexpress.nightcore:main:2.16.2") +} \ No newline at end of file diff --git a/impl/ExcellentEnchant5_4/src/main/java/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchant5_3Registry.java b/impl/ExcellentEnchant5_4/src/main/java/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchant5_3Registry.java new file mode 100644 index 0000000..51e7302 --- /dev/null +++ b/impl/ExcellentEnchant5_4/src/main/java/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchant5_3Registry.java @@ -0,0 +1,16 @@ +package xyz.alexcrea.cuanvil.dependency.plugins; + +import org.jetbrains.annotations.NotNull; +import su.nightexpress.excellentenchants.api.enchantment.CustomEnchantment; +import su.nightexpress.excellentenchants.enchantment.EnchantRegistry; + +import java.util.Set; + +public class ExcellentEnchant5_3Registry { + + public static @NotNull Set getRegistered(){ + return EnchantRegistry.getRegistered(); + } + + +} diff --git a/impl/ExcellentEnchant5_4/src/main/java/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchant5_4EnchantSettings.java b/impl/ExcellentEnchant5_4/src/main/java/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchant5_4EnchantSettings.java new file mode 100644 index 0000000..49dde3e --- /dev/null +++ b/impl/ExcellentEnchant5_4/src/main/java/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchant5_4EnchantSettings.java @@ -0,0 +1,12 @@ +package xyz.alexcrea.cuanvil.dependency.plugins; + +import su.nightexpress.excellentenchants.EnchantsAPI; + +public class ExcellentEnchant5_4EnchantSettings { + + + public static int anvilLimit() { + return EnchantsAPI.getEnchantManager().getSettings().getAnvilEnchantsLimit(); + } + +} diff --git a/impl/LegacyEcoEnchant/build.gradle.kts b/impl/LegacyEcoEnchant/build.gradle.kts index a53c04c..83057a7 100644 --- a/impl/LegacyEcoEnchant/build.gradle.kts +++ b/impl/LegacyEcoEnchant/build.gradle.kts @@ -2,7 +2,7 @@ group = rootProject.group version = rootProject.version plugins { - kotlin("jvm") version "2.1.0" + kotlin("jvm") version "2.3.0" } // Imitate needed class and method to support legacy version of EcoEnchant @@ -10,6 +10,4 @@ dependencies { // Spigot api compileOnly("org.spigotmc:spigot-api:1.18-R0.1-SNAPSHOT") - - } \ No newline at end of file diff --git a/libs/AxPlayerWarps-1.10.3.jar b/libs/AxPlayerWarps-1.10.3.jar new file mode 100644 index 0000000..a32b23c Binary files /dev/null and b/libs/AxPlayerWarps-1.10.3.jar differ diff --git a/libs/ExcellentEnchants 4.1.0-striped.jar b/libs/ExcellentEnchants-4.1.0-striped.jar similarity index 100% rename from libs/ExcellentEnchants 4.1.0-striped.jar rename to libs/ExcellentEnchants-4.1.0-striped.jar diff --git a/libs/ExcellentEnchants-4.3.1.jar b/libs/ExcellentEnchants-4.3.1.jar deleted file mode 100644 index 47ea157..0000000 Binary files a/libs/ExcellentEnchants-4.3.1.jar and /dev/null differ diff --git a/libs/ExcellentEnchants-4.3.3-striped.jar b/libs/ExcellentEnchants-4.3.3-striped.jar new file mode 100644 index 0000000..ff0656b Binary files /dev/null and b/libs/ExcellentEnchants-4.3.3-striped.jar differ diff --git a/libs/SuperEnchants-4.6.2-all.jar b/libs/SuperEnchants-4.6.2-all.jar new file mode 100644 index 0000000..f4c832a Binary files /dev/null and b/libs/SuperEnchants-4.6.2-all.jar differ diff --git a/libs/toolstats-1.9.6-stripped.jar b/libs/toolstats-1.9.6-stripped.jar new file mode 100644 index 0000000..eb17393 Binary files /dev/null and b/libs/toolstats-1.9.6-stripped.jar differ diff --git a/nms/nms-common/build.gradle.kts b/nms/nms-common/build.gradle.kts index d1f224d..950d807 100644 --- a/nms/nms-common/build.gradle.kts +++ b/nms/nms-common/build.gradle.kts @@ -1,16 +1,35 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + group = rootProject.group version = rootProject.version -repositories { - // ProtocoLib - maven (url = "https://repo.dmulloy2.net/repository/public/" ) - +plugins { + id("io.papermc.paperweight.userdev") } dependencies { - // Spigot api - compileOnly("org.spigotmc:spigot-api:1.18-R0.1-SNAPSHOT") + // Used for nms + paperweight.paperDevBundle("1.21.10-R0.1-SNAPSHOT") // Protocolib - compileOnly("com.comphenix.protocol:ProtocolLib:5.1.0") -} \ No newline at end of file + compileOnly("net.dmulloy2:ProtocolLib:5.4.0") +} + +repositories { + maven("https://repo.papermc.io/repository/maven-public/") +} + +// Set target version +tasks.withType().configureEach { + sourceCompatibility = "16" + targetCompatibility = "16" + + options.encoding = "UTF-8" +} + +kotlin { + compilerOptions { + apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2) + jvmTarget.set(JvmTarget.JVM_16) + } +} diff --git a/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/ExternGuiTester.kt b/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/ExternGuiTester.kt deleted file mode 100644 index 3e11a43..0000000 --- a/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/ExternGuiTester.kt +++ /dev/null @@ -1,24 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.gui - -import org.bukkit.inventory.InventoryView - -interface ExternGuiTester { - - val wesjdAnvilGuiName: String? - - fun getContainerClass(inventory: InventoryView): Class? - - fun testIfGui(inventory: InventoryView): Boolean { - val clazz = getContainerClass(inventory) - if(clazz == null) return false - - val expectedWesjdGuiPath = "anvilgui.version.$wesjdAnvilGuiName" - - val clazzName = clazz.name - val isWejdsGui = clazzName.contains(expectedWesjdGuiPath) - - return isWejdsGui - } - - -} \ No newline at end of file diff --git a/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/util/PaperSpigotUtil.kt b/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/util/PaperSpigotUtil.kt new file mode 100644 index 0000000..3f709c7 --- /dev/null +++ b/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/util/PaperSpigotUtil.kt @@ -0,0 +1,102 @@ +package xyz.alexcrea.cuanvil.dependency.util + +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer +import org.bukkit.inventory.ItemStack +import org.bukkit.inventory.meta.ItemMeta + +// Mostly made for paper, spigot and folia support +@Suppress("DEPRECATION") +object PlatformUtil { + + private fun hasClass(className: String): Boolean { + try { + Class.forName(className) + return true + } catch (_: ClassNotFoundException) { + return false + } + } + + private fun hasMethod(clazz: Class<*>, name: String, vararg parameterTypes: Class<*>): Boolean { + try { + clazz.getDeclaredMethod(name, *parameterTypes) + return true + } catch (_: NoSuchMethodException) { + return false + } + } + + val isPaper = hasClass("com.destroystokyo.paper.PaperConfig") || + hasClass("io.papermc.paper.configuration.Configuration") + + val isFolia = hasClass("io.papermc.paper.threadedregions.RegionizedServer") + + private val legacy_mm = LegacyComponentSerializer.legacySection() + + // Lore + fun ItemMeta.componentLore(): MutableList { + val lore: List? + if(isPaper){ + lore = this.lore() + } else { + val legacyLores = this.lore ?: return ArrayList() + + lore = ArrayList(legacyLores.size) + for (legacyLore in legacyLores) { + lore.add(legacy_mm.deserialize(legacyLore)) + } + } + + return lore ?: ArrayList() + } + + fun ItemMeta.setComponentLore(lore: List) { + if(isPaper){ + this.lore(lore) + } else { + val legacyLore = ArrayList(lore.size) + for (component in lore) { + legacyLore.add(if(component == null) null + else legacy_mm.serialize(component)) + } + + this.lore = legacyLore + } + } + + // Display name + private val useCustomName = hasMethod(ItemStack::class.java, "customName") + + fun ItemMeta.componentDisplayName(): Component? { + if(useCustomName){ + if(!this.hasCustomName()) return null + return this.customName() + }else if(isPaper){ + if(!this.hasDisplayName()) return null + return this.displayName() + } else { + if(!this.hasDisplayName()) return null + + val legacy = this.displayName + return legacy_mm.deserialize(legacy) + } + } + + fun ItemMeta.setComponentDisplayName(component: Component?) { + if(useCustomName){ + this.customName(component) + }else if(isPaper){ + this.displayName(component) + } else { + if(component == null){ + this.setDisplayName(null) + return + } + + val legacy = legacy_mm.serialize(component) + this.setDisplayName(legacy) + } + } + +} diff --git a/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dialog/AnvilRenameDialog.kt b/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dialog/AnvilRenameDialog.kt new file mode 100644 index 0000000..df6e883 --- /dev/null +++ b/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dialog/AnvilRenameDialog.kt @@ -0,0 +1,23 @@ +package xyz.alexcrea.cuanvil.dialog + +import org.bukkit.NamespacedKey +import org.bukkit.entity.HumanEntity +import org.bukkit.event.inventory.PrepareAnvilEvent + +interface AnvilRenameDialog { + + companion object { + val PCD_KEEP_RENAME_TEXT_KEY = NamespacedKey.fromString("customanvil:last_rename_text")!! + } + + fun canSendDialog(): Boolean + + fun tryShowDialog(player: HumanEntity, event: PrepareAnvilEvent) + + fun closeInventory(player: HumanEntity) + + fun currentText(player: HumanEntity): String? + + fun isOpenFor(player: HumanEntity): Boolean + +} \ No newline at end of file diff --git a/nms/nms-paper/.gitignore b/nms/nms-paper/.gitignore new file mode 100644 index 0000000..47374f1 --- /dev/null +++ b/nms/nms-paper/.gitignore @@ -0,0 +1 @@ +.lastDeploymentsId \ No newline at end of file diff --git a/nms/nms-paper/build.gradle.kts b/nms/nms-paper/build.gradle.kts new file mode 100644 index 0000000..d0e12a7 --- /dev/null +++ b/nms/nms-paper/build.gradle.kts @@ -0,0 +1,35 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +group = rootProject.group +version = rootProject.version + +plugins { + id("io.papermc.paperweight.userdev") +} + +dependencies { + implementation(project(":nms:nms-common")) + + // Used for nms + paperweight.paperDevBundle("1.21.7-R0.1-SNAPSHOT") +} + +repositories { + maven("https://repo.papermc.io/repository/maven-public/") + +} + +// Set target version +tasks.withType().configureEach { + sourceCompatibility = "18" + targetCompatibility = "18" + + options.encoding = "UTF-8" +} + +kotlin { + compilerOptions { + apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2) + jvmTarget.set(JvmTarget.JVM_18) + } +} diff --git a/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/datapack/DataPackTester.kt b/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/datapack/DataPackTester.kt new file mode 100644 index 0000000..9003fdf --- /dev/null +++ b/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/datapack/DataPackTester.kt @@ -0,0 +1,38 @@ +package xyz.alexcrea.cuanvil.dependency.datapack + +import io.papermc.paper.datapack.Datapack +import org.bukkit.Bukkit +import org.bukkit.packs.DataPack +import java.util.* + +object DataPackTester { + val legacyNames: List + get() = Bukkit.getDataPackManager().dataPacks + .stream().filter { obj -> obj.isEnabled } + .map { pack -> pack.key.key } + .toList() + + val enabledPacks: List + get() { + try { + // will throw error if do not exist + Bukkit::class.java.getDeclaredMethod("getDatapackManager") + + return Bukkit.getDatapackManager().enabledPacks + .stream().map { obj: Datapack -> obj.name } + .toList() + } catch (e: NoSuchMethodException) { + try { + DataPack::class.java.getDeclaredMethod("getKey") + } catch (e: NoSuchMethodException) { + System.err.println("Could not find compatible datapack manager") + System.err.println("If you are using a datapack that should be compatible with CustomAnvil. It will not get detected...") + return emptyList() + } + return legacyNames + } catch (e: Exception){ + // Assume cause UnimplementedOperationException on mock server + return Collections.emptyList() + } + } +} diff --git a/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/PaperPacketManager.kt b/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/PaperPacketManager.kt new file mode 100644 index 0000000..ee0c101 --- /dev/null +++ b/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/PaperPacketManager.kt @@ -0,0 +1,33 @@ +package xyz.alexcrea.cuanvil.dependency.packet.versions + +import net.minecraft.network.protocol.game.ClientboundPlayerAbilitiesPacket +import net.minecraft.world.entity.player.Abilities +import org.bukkit.craftbukkit.entity.CraftPlayer +import org.bukkit.entity.Player +import xyz.alexcrea.cuanvil.dependency.packet.PacketManager +import xyz.alexcrea.cuanvil.dependency.packet.PacketManagerBase + +class PaperPacketManager : PacketManagerBase(), PacketManager { + override val canSetInstantBuild: Boolean + get() = true + + override fun setInstantBuild(player: Player, instantBuild: Boolean) { + val nmsPlayer = (player as CraftPlayer).handle + val playerAbilities = nmsPlayer.abilities + val sendedAbilities: Abilities + if (playerAbilities.instabuild == instantBuild) { + sendedAbilities = playerAbilities + } else { + sendedAbilities = Abilities() + sendedAbilities.invulnerable = playerAbilities.invulnerable + sendedAbilities.flying = playerAbilities.flying + sendedAbilities.mayfly = playerAbilities.mayfly + sendedAbilities.instabuild = instantBuild + sendedAbilities.mayBuild = playerAbilities.mayBuild + sendedAbilities.setFlyingSpeed(playerAbilities.getFlyingSpeed()) + sendedAbilities.setWalkingSpeed(playerAbilities.getWalkingSpeed()) + } + val packet = ClientboundPlayerAbilitiesPacket(sendedAbilities) + nmsPlayer.connection.send(packet) + } +} diff --git a/nms/v1_20R3/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/scheduler/FoliaScheduler.kt b/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/scheduler/FoliaScheduler.kt similarity index 100% rename from nms/v1_20R3/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/scheduler/FoliaScheduler.kt rename to nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/scheduler/FoliaScheduler.kt diff --git a/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/dialog/AnvilRenameDialogImpl.kt b/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/dialog/AnvilRenameDialogImpl.kt new file mode 100644 index 0000000..e210daa --- /dev/null +++ b/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/dialog/AnvilRenameDialogImpl.kt @@ -0,0 +1,236 @@ +package xyz.alexcrea.cuanvil.dialog + +import io.papermc.paper.dialog.Dialog +import io.papermc.paper.registry.data.dialog.ActionButton +import io.papermc.paper.registry.data.dialog.DialogBase +import io.papermc.paper.registry.data.dialog.action.DialogAction +import io.papermc.paper.registry.data.dialog.body.DialogBody +import io.papermc.paper.registry.data.dialog.input.DialogInput +import io.papermc.paper.registry.data.dialog.type.DialogType +import io.papermc.paper.threadedregions.scheduler.ScheduledTask +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.event.ClickCallback +import net.kyori.adventure.text.format.TextColor +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer +import net.minecraft.world.inventory.AnvilMenu +import org.bukkit.craftbukkit.event.CraftEventFactory +import org.bukkit.craftbukkit.inventory.CraftInventoryView +import org.bukkit.craftbukkit.inventory.view.CraftAnvilView +import org.bukkit.entity.HumanEntity +import org.bukkit.event.inventory.PrepareAnvilEvent +import org.bukkit.inventory.InventoryView +import org.bukkit.inventory.ItemStack +import org.bukkit.persistence.PersistentDataType +import org.bukkit.plugin.Plugin +import java.util.* +import java.util.function.BiFunction +import java.util.function.Consumer +import java.util.function.Supplier + +@Suppress("UnstableApiUsage") +class AnvilRenameDialogImpl( + val fromFormated: BiFunction, + val keepUserPreviousDialog: Supplier, + val maxLength: Supplier, + val plugin: Plugin, +) : AnvilRenameDialog { + + companion object { + private const val RENAME_TEXT_KEY = "rename" + + private const val MAX_WIDTH = 512 + + private val PLAIN_TEXT_SERIALIZER = PlainTextComponentSerializer.plainText() + + // Need to be able to translate it later ! + private val USER_FACING_RENAME_TITLE = Component.text("Rename Your Item") + private val USER_FACING_WARNING = Component.text( + "Note that the repair text will appear blank after Confirm\n" + + "But the name will be correctly applied" + ) + private val USER_FACING_CONFIRM = Component.text("Confirm").color(TextColor.fromHexString("#40FF40")) + private val USER_FACING_CANCEL = Component.text("Cancel").color(TextColor.fromHexString("#FF4040")) + + fun itemDefaultName(item: ItemStack?): String? { + return PLAIN_TEXT_SERIALIZER.serializeOrNull(item?.effectiveName()) + } + } + + private val lastNames = HashMap() + private val lastRenames = HashMap() + + + private val lastLeftItem = HashMap() + private val runTaskMap = HashMap() + + // For monetary cost + val hasUiOpen = HashSet() + + private val containerField = CraftInventoryView::class.java.getDeclaredField("container") + + init { + containerField.setAccessible(true) + } + + override fun canSendDialog(): Boolean { + return true + } + + fun makeDialog(playerID: UUID, initial: String?, callback: Consumer): Dialog { + val maxLength = this.maxLength.get() + val initialFinal = initial?.take(maxLength) + + val baseBuilder = DialogBase.builder(USER_FACING_RENAME_TITLE) + .canCloseWithEscape(true) + .afterAction(DialogBase.DialogAfterAction.CLOSE) + .inputs( + listOf( + DialogInput.text(RENAME_TEXT_KEY, Component.text("Rename text")) + .maxLength(maxLength) + .initial(initialFinal ?: "") + .labelVisible(false) + .width(MAX_WIDTH) + .build(), + ), + ) + baseBuilder.body( + listOf( + DialogBody.plainMessage(USER_FACING_WARNING, MAX_WIDTH) + ) + ) + + return Dialog.create { builder -> + builder.empty() + .base(baseBuilder.build()) + .type( + DialogType.confirmation( + ActionButton.builder(USER_FACING_CONFIRM) + .action(DialogAction.customClick({ response, _ -> + hasUiOpen.remove(playerID) + val text = response.getText(RENAME_TEXT_KEY)!! + callback.accept(text) + }, ClickCallback.Options.builder().build())) + .build(), + ActionButton.builder(USER_FACING_CANCEL) + .action(DialogAction.customClick({ response, _ -> + hasUiOpen.remove(playerID) + }, ClickCallback.Options.builder().build())) + .build(), + ) + ) + } + } + + private fun setResult(player: HumanEntity, view: InventoryView, result: String?) { + val defaultName = itemDefaultName(view.getItem(0)) + if (defaultName == result) { + setName(player, view, "", null) + if (defaultName != null) lastNames[player.uniqueId] = defaultName + } else setName(player, view, result, result) + } + + private fun setName(player: HumanEntity, view: InventoryView, name: String?, rename: String?) { + val menu = (containerField.get(view) as AnvilMenu) + val isSameName = menu.itemName == name + menu.itemName = rename + + if (name == null) + lastNames.remove(player.uniqueId) + else + lastNames[player.uniqueId] = name + + if (rename == null) + lastRenames.remove(player.uniqueId) + else + lastRenames[player.uniqueId] = rename + + if (!isSameName) + CraftEventFactory.callPrepareResultEvent(menu, 2); + } + + private fun nameFromItem(player: HumanEntity, item: ItemStack?): String? { + // Already has text + if (item?.hasItemMeta() != true || !item.itemMeta.hasCustomName()) + return PLAIN_TEXT_SERIALIZER.serializeOrNull(item?.effectiveName()) + + if (keepUserPreviousDialog.get() && item.hasItemMeta()) { + val lastName = item.itemMeta.persistentDataContainer.get( + AnvilRenameDialog.PCD_KEEP_RENAME_TEXT_KEY, + PersistentDataType.STRING + ) + + if (lastName != null) return lastName + } + + return fromFormated.apply(player, item.effectiveName()) + } + + private fun tryShowDialogScheduled(player: HumanEntity, event: PrepareAnvilEvent) { + val view = event.view + if (view !is CraftAnvilView) return + + val renameText = view.renameText + val leftItem = view.getItem(0) + val leftItemStr = leftItem?.toString() + + val lastName = lastNames.getOrDefault(player.uniqueId, null) + val lastRename = lastRenames.getOrDefault(player.uniqueId, null) + + if (lastLeftItem.getOrDefault(player.uniqueId, null) != leftItemStr) { + if (leftItemStr == null) + lastLeftItem.remove(player.uniqueId) + else lastLeftItem[player.uniqueId] = leftItemStr + + setName(player, view, renameText, nameFromItem(player, leftItem)) + return + } + + if (lastName == renameText || lastRename == renameText) + return + + if (renameText?.isBlank() == true || renameText == itemDefaultName(leftItem)) { + setName(player, view, lastName, lastRename) + return + } + + val dialog = makeDialog(player.uniqueId, lastRename) + { result -> setResult(player, view, result) } + player.showDialog(dialog) + + hasUiOpen.add(player.uniqueId) + } + + // We need to wait for a short time as changing item will change the name BEFORE the item change + // no guaranty both of them came in the same tick too so let's wait 2 tick.... + override fun tryShowDialog(player: HumanEntity, event: PrepareAnvilEvent) { + runTaskMap.remove(player.uniqueId)?.cancel() + + val task = player.scheduler.runDelayed( + plugin, + { _ -> + run { tryShowDialogScheduled(player, event) } + }, + {}, + 2 + ) + if (task == null) return + + runTaskMap[player.uniqueId] = task + } + + override fun closeInventory(player: HumanEntity) { + lastNames.remove(player.uniqueId) + lastRenames.remove(player.uniqueId) + lastLeftItem.remove(player.uniqueId) + runTaskMap.remove(player.uniqueId)?.cancel() + } + + override fun currentText(player: HumanEntity): String? { + return lastNames[player.uniqueId] + } + + override fun isOpenFor(player: HumanEntity): Boolean { + return hasUiOpen.contains(player.uniqueId) + } + +} \ No newline at end of file diff --git a/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilTitleUtil.kt b/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilTitleUtil.kt new file mode 100644 index 0000000..e40e7ed --- /dev/null +++ b/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilTitleUtil.kt @@ -0,0 +1,45 @@ +package xyz.alexcrea.cuanvil.util + +import io.papermc.paper.threadedregions.scheduler.ScheduledTask +import org.bukkit.entity.HumanEntity +import org.bukkit.inventory.InventoryView +import org.bukkit.plugin.Plugin +import xyz.alexcrea.cuanvil.dialog.AnvilRenameDialog +import java.util.HashMap +import java.util.UUID + +object AnvilTitleUtil { + + private val runTaskMap = HashMap() + + private fun actualRename(view: InventoryView, name: String, player: HumanEntity, anvilDialog: AnvilRenameDialog) { + runTaskMap.remove(player.uniqueId) + if (view.title == name) return + + // We assume rename impl is used + if (anvilDialog.isOpenFor(player)) return + + view.title = name + } + + // We don't want to rename instantly it is causing issue with rename text + // especially as it can "override" current ui when it is rename ui time but rename ui also need some delay + fun rename(view: InventoryView, name: String, player: HumanEntity, anvilDialog: AnvilRenameDialog, plugin: Plugin) { + runTaskMap.remove(player.uniqueId)?.cancel() + + val task = player.scheduler.runDelayed( + plugin, + { _ -> + run { actualRename(view, name, player, anvilDialog) } + }, + { + runTaskMap.remove(player.uniqueId) + }, + 2 + ) + + if (task == null) return + runTaskMap[player.uniqueId] = task + } + +} \ No newline at end of file diff --git a/nms/v1_17R1/build.gradle.kts b/nms/v1_17R1/build.gradle.kts index 0480a4b..c354b1c 100644 --- a/nms/v1_17R1/build.gradle.kts +++ b/nms/v1_17R1/build.gradle.kts @@ -19,14 +19,6 @@ repositories { } -// As minecraft 1.17 recommended java version is 1.16. we set language version to 1.16 - -// Configure used version of kotlin and java -java { - disableAutoTargetJvm() - toolchain.languageVersion.set(JavaLanguageVersion.of(17)) -} - // Set target version tasks.withType().configureEach { sourceCompatibility = "16" @@ -37,7 +29,7 @@ tasks.withType().configureEach { kotlin { compilerOptions { - apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0) + apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2) jvmTarget.set(JvmTarget.JVM_16) } } diff --git a/nms/v1_17R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_17R1_ExternGuiTester.kt b/nms/v1_17R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_17R1_ExternGuiTester.kt deleted file mode 100644 index 8e352e0..0000000 --- a/nms/v1_17R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_17R1_ExternGuiTester.kt +++ /dev/null @@ -1,16 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.gui.version - -import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftInventoryView -import org.bukkit.inventory.InventoryView -import xyz.alexcrea.cuanvil.dependency.gui.ExternGuiTester - -class v1_17R1_ExternGuiTester: ExternGuiTester { - override val wesjdAnvilGuiName = "Wrapper1_17_R1" - - override fun getContainerClass(view: InventoryView): Class? { - if (view !is CraftInventoryView) return null - val container = view.handle - - return container.javaClass - } -} \ No newline at end of file diff --git a/nms/v1_18R1/build.gradle.kts b/nms/v1_18R1/build.gradle.kts index cab1611..44873d5 100644 --- a/nms/v1_18R1/build.gradle.kts +++ b/nms/v1_18R1/build.gradle.kts @@ -19,14 +19,6 @@ repositories { } -// As minecraft 1.18 work with java 1.17 or above. we set language version to 1.17 - -// Configure used version of kotlin and java -java { - disableAutoTargetJvm() - toolchain.languageVersion.set(JavaLanguageVersion.of(17)) -} - // Set target version tasks.withType().configureEach { sourceCompatibility = "17" @@ -37,7 +29,7 @@ tasks.withType().configureEach { kotlin { compilerOptions { - apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0) + apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2) jvmTarget.set(JvmTarget.JVM_17) } } diff --git a/nms/v1_18R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_18R1_ExternGuiTester.kt b/nms/v1_18R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_18R1_ExternGuiTester.kt deleted file mode 100644 index 659a0f6..0000000 --- a/nms/v1_18R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_18R1_ExternGuiTester.kt +++ /dev/null @@ -1,16 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.gui.version - -import org.bukkit.craftbukkit.v1_18_R1.inventory.CraftInventoryView -import org.bukkit.inventory.InventoryView -import xyz.alexcrea.cuanvil.dependency.gui.ExternGuiTester - -class v1_18R1_ExternGuiTester: ExternGuiTester { - override val wesjdAnvilGuiName = "Wrapper1_18_R1" - - override fun getContainerClass(view: InventoryView): Class? { - if (view !is CraftInventoryView) return null - val container = view.handle - - return container.javaClass - } -} \ No newline at end of file diff --git a/nms/v1_18R2/build.gradle.kts b/nms/v1_18R2/build.gradle.kts index 9f7c5a5..cf349ec 100644 --- a/nms/v1_18R2/build.gradle.kts +++ b/nms/v1_18R2/build.gradle.kts @@ -19,14 +19,6 @@ repositories { } -// As minecraft 1.18 work with java 1.17 or above. we set language version to 1.17 - -// Configure used version of kotlin and java -java { - disableAutoTargetJvm() - toolchain.languageVersion.set(JavaLanguageVersion.of(17)) -} - // Set target version tasks.withType().configureEach { sourceCompatibility = "17" @@ -37,7 +29,7 @@ tasks.withType().configureEach { kotlin { compilerOptions { - apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0) + apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2) jvmTarget.set(JvmTarget.JVM_17) } } diff --git a/nms/v1_18R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_18R2_ExternGuiTester.kt b/nms/v1_18R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_18R2_ExternGuiTester.kt deleted file mode 100644 index 1447716..0000000 --- a/nms/v1_18R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_18R2_ExternGuiTester.kt +++ /dev/null @@ -1,16 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.gui.version - -import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftInventoryView -import org.bukkit.inventory.InventoryView -import xyz.alexcrea.cuanvil.dependency.gui.ExternGuiTester - -class v1_18R2_ExternGuiTester: ExternGuiTester { - override val wesjdAnvilGuiName = "Wrapper1_18_R2" - - override fun getContainerClass(view: InventoryView): Class? { - if (view !is CraftInventoryView) return null - val container = view.handle - - return container.javaClass - } -} \ No newline at end of file diff --git a/nms/v1_19R1/build.gradle.kts b/nms/v1_19R1/build.gradle.kts index b9a5a1a..9791b9b 100644 --- a/nms/v1_19R1/build.gradle.kts +++ b/nms/v1_19R1/build.gradle.kts @@ -19,14 +19,6 @@ repositories { } -// I do not know minecraft 1.19 recommended java version. assumed 17 is good enough - -// Configure used version of kotlin and java -java { - disableAutoTargetJvm() - toolchain.languageVersion.set(JavaLanguageVersion.of(17)) -} - // Set target version tasks.withType().configureEach { sourceCompatibility = "17" @@ -37,7 +29,7 @@ tasks.withType().configureEach { kotlin { compilerOptions { - apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0) + apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2) jvmTarget.set(JvmTarget.JVM_17) } } diff --git a/nms/v1_19R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_19R1_ExternGuiTester.kt b/nms/v1_19R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_19R1_ExternGuiTester.kt deleted file mode 100644 index c151924..0000000 --- a/nms/v1_19R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_19R1_ExternGuiTester.kt +++ /dev/null @@ -1,16 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.gui.version - -import org.bukkit.craftbukkit.v1_19_R1.inventory.CraftInventoryView -import org.bukkit.inventory.InventoryView -import xyz.alexcrea.cuanvil.dependency.gui.ExternGuiTester - -class v1_19R1_ExternGuiTester: ExternGuiTester { - override val wesjdAnvilGuiName = "Wrapper1_19_R1" - - override fun getContainerClass(view: InventoryView): Class? { - if (view !is CraftInventoryView) return null - val container = view.handle - - return container.javaClass - } -} \ No newline at end of file diff --git a/nms/v1_19R2/build.gradle.kts b/nms/v1_19R2/build.gradle.kts index a4df488..88f1a8a 100644 --- a/nms/v1_19R2/build.gradle.kts +++ b/nms/v1_19R2/build.gradle.kts @@ -19,14 +19,6 @@ repositories { } -// I do not know minecraft 1.19 recommended java version. assumed 17 is good enough - -// Configure used version of kotlin and java -java { - disableAutoTargetJvm() - toolchain.languageVersion.set(JavaLanguageVersion.of(17)) -} - // Set target version tasks.withType().configureEach { sourceCompatibility = "17" @@ -37,7 +29,7 @@ tasks.withType().configureEach { kotlin { compilerOptions { - apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0) + apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2) jvmTarget.set(JvmTarget.JVM_17) } } diff --git a/nms/v1_19R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_19R2_ExternGuiTester.kt b/nms/v1_19R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_19R2_ExternGuiTester.kt deleted file mode 100644 index ac46674..0000000 --- a/nms/v1_19R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_19R2_ExternGuiTester.kt +++ /dev/null @@ -1,16 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.gui.version - -import org.bukkit.craftbukkit.v1_19_R2.inventory.CraftInventoryView -import org.bukkit.inventory.InventoryView -import xyz.alexcrea.cuanvil.dependency.gui.ExternGuiTester - -class v1_19R2_ExternGuiTester: ExternGuiTester { - override val wesjdAnvilGuiName = "Wrapper1_19_R2" - - override fun getContainerClass(view: InventoryView): Class? { - if (view !is CraftInventoryView) return null - val container = view.handle - - return container.javaClass - } -} \ No newline at end of file diff --git a/nms/v1_19R3/build.gradle.kts b/nms/v1_19R3/build.gradle.kts index 84476a3..3d609af 100644 --- a/nms/v1_19R3/build.gradle.kts +++ b/nms/v1_19R3/build.gradle.kts @@ -19,14 +19,6 @@ repositories { } -// I do not know minecraft 1.19 recommended java version. assumed 17 is good enough - -// Configure used version of kotlin and java -java { - disableAutoTargetJvm() - toolchain.languageVersion.set(JavaLanguageVersion.of(17)) -} - // Set target version tasks.withType().configureEach { sourceCompatibility = "17" @@ -37,7 +29,7 @@ tasks.withType().configureEach { kotlin { compilerOptions { - apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0) + apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2) jvmTarget.set(JvmTarget.JVM_17) } } diff --git a/nms/v1_19R3/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_19R3_ExternGuiTester.kt b/nms/v1_19R3/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_19R3_ExternGuiTester.kt deleted file mode 100644 index 7ce5abd..0000000 --- a/nms/v1_19R3/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_19R3_ExternGuiTester.kt +++ /dev/null @@ -1,16 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.gui.version - -import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftInventoryView -import org.bukkit.inventory.InventoryView -import xyz.alexcrea.cuanvil.dependency.gui.ExternGuiTester - -class v1_19R3_ExternGuiTester: ExternGuiTester { - override val wesjdAnvilGuiName = "Wrapper1_19_R3" - - override fun getContainerClass(view: InventoryView): Class? { - if (view !is CraftInventoryView) return null - val container = view.handle - - return container.javaClass - } -} \ No newline at end of file diff --git a/nms/v1_20R1/build.gradle.kts b/nms/v1_20R1/build.gradle.kts index e71d674..67b46b5 100644 --- a/nms/v1_20R1/build.gradle.kts +++ b/nms/v1_20R1/build.gradle.kts @@ -19,25 +19,17 @@ repositories { } -// minecraft 1.20 recommended java version is 18. but we assume 17 is good enough as lts - -// Configure used version of kotlin and java -java { - disableAutoTargetJvm() - toolchain.languageVersion.set(JavaLanguageVersion.of(17)) -} - // Set target version tasks.withType().configureEach { - sourceCompatibility = "17" - targetCompatibility = "17" + sourceCompatibility = "18" + targetCompatibility = "18" options.encoding = "UTF-8" } kotlin { compilerOptions { - apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0) - jvmTarget.set(JvmTarget.JVM_17) + apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2) + jvmTarget.set(JvmTarget.JVM_18) } } diff --git a/nms/v1_20R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_20R1_ExternGuiTester.kt b/nms/v1_20R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_20R1_ExternGuiTester.kt deleted file mode 100644 index dae3b98..0000000 --- a/nms/v1_20R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_20R1_ExternGuiTester.kt +++ /dev/null @@ -1,16 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.gui.version - -import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventoryView -import org.bukkit.inventory.InventoryView -import xyz.alexcrea.cuanvil.dependency.gui.ExternGuiTester - -class v1_20R1_ExternGuiTester: ExternGuiTester { - override val wesjdAnvilGuiName = "Wrapper1_20_R1" - - override fun getContainerClass(view: InventoryView): Class? { - if (view !is CraftInventoryView) return null - val container = view.handle - - return container.javaClass - } -} diff --git a/nms/v1_20R2/build.gradle.kts b/nms/v1_20R2/build.gradle.kts index 47ebfce..e8417cd 100644 --- a/nms/v1_20R2/build.gradle.kts +++ b/nms/v1_20R2/build.gradle.kts @@ -19,25 +19,17 @@ repositories { } -// minecraft 1.20 recommended java version is 18. but we assume 17 is good enough as lts - -// Configure used version of kotlin and java -java { - disableAutoTargetJvm() - toolchain.languageVersion.set(JavaLanguageVersion.of(17)) -} - // Set target version tasks.withType().configureEach { - sourceCompatibility = "17" - targetCompatibility = "17" + sourceCompatibility = "18" + targetCompatibility = "18" options.encoding = "UTF-8" } kotlin { compilerOptions { - apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0) - jvmTarget.set(JvmTarget.JVM_17) + apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2) + jvmTarget.set(JvmTarget.JVM_18) } } diff --git a/nms/v1_20R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_20R2_ExternGuiTester.kt b/nms/v1_20R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_20R2_ExternGuiTester.kt deleted file mode 100644 index 6a8358a..0000000 --- a/nms/v1_20R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_20R2_ExternGuiTester.kt +++ /dev/null @@ -1,17 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.gui.version - -import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftInventoryView -import org.bukkit.inventory.InventoryView -import xyz.alexcrea.cuanvil.dependency.gui.ExternGuiTester -import kotlin.jvm.javaClass - -class v1_20R2_ExternGuiTester: ExternGuiTester { - override val wesjdAnvilGuiName = "Wrapper1_20_R2" - - override fun getContainerClass(view: InventoryView): Class? { - if (view !is CraftInventoryView) return null - val container = view.handle - - return container.javaClass - } -} diff --git a/nms/v1_20R3/build.gradle.kts b/nms/v1_20R3/build.gradle.kts index eb4d3c3..c49ff48 100644 --- a/nms/v1_20R3/build.gradle.kts +++ b/nms/v1_20R3/build.gradle.kts @@ -19,25 +19,17 @@ repositories { } -// minecraft 1.20 recommended java version is 18. but we assume 17 is good enough as lts - -// Configure used version of kotlin and java -java { - disableAutoTargetJvm() - toolchain.languageVersion.set(JavaLanguageVersion.of(17)) -} - // Set target version tasks.withType().configureEach { - sourceCompatibility = "17" - targetCompatibility = "17" + sourceCompatibility = "18" + targetCompatibility = "18" options.encoding = "UTF-8" } kotlin { compilerOptions { - apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0) - jvmTarget.set(JvmTarget.JVM_17) + apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2) + jvmTarget.set(JvmTarget.JVM_18) } } diff --git a/nms/v1_20R3/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_20R3_ExternGuiTester.kt b/nms/v1_20R3/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_20R3_ExternGuiTester.kt deleted file mode 100644 index 80362e2..0000000 --- a/nms/v1_20R3/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_20R3_ExternGuiTester.kt +++ /dev/null @@ -1,17 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.gui.version - -import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftInventoryView -import org.bukkit.inventory.InventoryView -import xyz.alexcrea.cuanvil.dependency.gui.ExternGuiTester -import kotlin.jvm.javaClass - -class v1_20R3_ExternGuiTester: ExternGuiTester { - override val wesjdAnvilGuiName = "Wrapper1_20_R3" - - override fun getContainerClass(view: InventoryView): Class? { - if (view !is CraftInventoryView) return null - val container = view.handle - - return container.javaClass - } -} diff --git a/nms/v1_20R4/build.gradle.kts b/nms/v1_20R4/build.gradle.kts index 00f5c68..3b98361 100644 --- a/nms/v1_20R4/build.gradle.kts +++ b/nms/v1_20R4/build.gradle.kts @@ -19,18 +19,10 @@ repositories { } -// minecraft 1.20 recommended java version is 18. but we assume 17 is good enough as lts - -// Configure used version of kotlin and java -java { - disableAutoTargetJvm() - toolchain.languageVersion.set(JavaLanguageVersion.of(17)) -} - // Set target version tasks.withType().configureEach { - sourceCompatibility = "17" - targetCompatibility = "17" + sourceCompatibility = "18" + targetCompatibility = "18" options.encoding = "UTF-8" } @@ -38,6 +30,6 @@ tasks.withType().configureEach { kotlin { compilerOptions { apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0) - jvmTarget.set(JvmTarget.JVM_17) + jvmTarget.set(JvmTarget.JVM_18) } } diff --git a/nms/v1_20R4/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_20R4_ExternGuiTester.kt b/nms/v1_20R4/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_20R4_ExternGuiTester.kt deleted file mode 100644 index 16e867c..0000000 --- a/nms/v1_20R4/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_20R4_ExternGuiTester.kt +++ /dev/null @@ -1,17 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.gui.version - -import org.bukkit.craftbukkit.inventory.CraftInventoryView -import org.bukkit.inventory.InventoryView -import xyz.alexcrea.cuanvil.dependency.gui.ExternGuiTester -import kotlin.jvm.javaClass - -class v1_20R4_ExternGuiTester: ExternGuiTester { - override val wesjdAnvilGuiName = "Wrapper1_20_R4" - - override fun getContainerClass(view: InventoryView): Class? { - if (view !is CraftInventoryView) return null - val container = view.handle - - return container.javaClass - } -} diff --git a/nms/v1_20R4/src/main/kotlin/xyz/alexcrea/cuanvil/util/MaxDamageCheckerUtil.kt b/nms/v1_20R4/src/main/kotlin/xyz/alexcrea/cuanvil/util/MaxDamageCheckerUtil.kt new file mode 100644 index 0000000..b3ce0ff --- /dev/null +++ b/nms/v1_20R4/src/main/kotlin/xyz/alexcrea/cuanvil/util/MaxDamageCheckerUtil.kt @@ -0,0 +1,18 @@ +package xyz.alexcrea.cuanvil.util + +import org.bukkit.inventory.meta.Damageable + +// I LOVE support of old versions and needing to do modules like that +// That truly is my favorite activity +// TODO clean this one of legacy removal branch +object MaxDamageCheckerUtil { + + /** + * @return max damage or int max if not set + */ + fun getMaxDamage(meta: Damageable): Int { + if(!meta.hasMaxDamage()) return Integer.MAX_VALUE + return meta.maxDamage + } + +} \ No newline at end of file diff --git a/nms/v1_21R1/build.gradle.kts b/nms/v1_21R1/build.gradle.kts index 435d09c..bf2152c 100644 --- a/nms/v1_21R1/build.gradle.kts +++ b/nms/v1_21R1/build.gradle.kts @@ -19,14 +19,6 @@ repositories { } -// minecraft 1.21 java version is 21. - -// Configure used version of kotlin and java -java { - disableAutoTargetJvm() - toolchain.languageVersion.set(JavaLanguageVersion.of(21)) -} - // Set target version tasks.withType().configureEach { sourceCompatibility = "21" @@ -37,7 +29,7 @@ tasks.withType().configureEach { kotlin { compilerOptions { - apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0) + apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2) jvmTarget.set(JvmTarget.JVM_21) } } diff --git a/nms/v1_21R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_21R1_ExternGuiTester.kt b/nms/v1_21R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_21R1_ExternGuiTester.kt deleted file mode 100644 index eeee7ec..0000000 --- a/nms/v1_21R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_21R1_ExternGuiTester.kt +++ /dev/null @@ -1,34 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.gui.version - -import org.bukkit.craftbukkit.inventory.CraftInventoryView -import org.bukkit.inventory.InventoryView -import xyz.alexcrea.cuanvil.dependency.gui.ExternGuiTester - -class v1_21R1_ExternGuiTester: ExternGuiTester { - override val wesjdAnvilGuiName = "Wrapper1_21_R1" - - var tested = false; - var possible = false; - - override fun getContainerClass(view: InventoryView): Class? { - // In case we are in a test environment - if(!tested) testClassExist() - if(!possible) return null - - if(view !is CraftInventoryView<*, *>) return null - val container = view.handle - - return container.javaClass - } - - fun testClassExist(){ - tested = true; - try { - Class.forName("org.bukkit.craftbukkit.inventory.CraftInventoryView") - possible = true - } catch (e: ClassNotFoundException){ - possible = false - } - } - -} diff --git a/nms/v1_21R2/build.gradle.kts b/nms/v1_21R2/build.gradle.kts index 36dc7ac..e65f6fd 100644 --- a/nms/v1_21R2/build.gradle.kts +++ b/nms/v1_21R2/build.gradle.kts @@ -19,14 +19,6 @@ repositories { } -// minecraft 1.21 java version is 21. - -// Configure used version of kotlin and java -java { - disableAutoTargetJvm() - toolchain.languageVersion.set(JavaLanguageVersion.of(21)) -} - // Set target version tasks.withType().configureEach { sourceCompatibility = "21" @@ -37,7 +29,7 @@ tasks.withType().configureEach { kotlin { compilerOptions { - apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0) + apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2) jvmTarget.set(JvmTarget.JVM_21) } } diff --git a/nms/v1_21R3/build.gradle.kts b/nms/v1_21R3/build.gradle.kts index 19d2ffd..d06816c 100644 --- a/nms/v1_21R3/build.gradle.kts +++ b/nms/v1_21R3/build.gradle.kts @@ -19,14 +19,6 @@ repositories { } -// minecraft 1.21 java version is 21. - -// Configure used version of kotlin and java -java { - disableAutoTargetJvm() - toolchain.languageVersion.set(JavaLanguageVersion.of(21)) -} - // Set target version tasks.withType().configureEach { sourceCompatibility = "21" @@ -37,7 +29,7 @@ tasks.withType().configureEach { kotlin { compilerOptions { - apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0) + apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2) jvmTarget.set(JvmTarget.JVM_21) } } diff --git a/nms/v1_21R4/.gitignore b/nms/v1_21R4/.gitignore new file mode 100644 index 0000000..47374f1 --- /dev/null +++ b/nms/v1_21R4/.gitignore @@ -0,0 +1 @@ +.lastDeploymentsId \ No newline at end of file diff --git a/nms/v1_21R4/build.gradle.kts b/nms/v1_21R4/build.gradle.kts new file mode 100644 index 0000000..8bd22b9 --- /dev/null +++ b/nms/v1_21R4/build.gradle.kts @@ -0,0 +1,34 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +group = rootProject.group +version = rootProject.version + +plugins { + id("io.papermc.paperweight.userdev") +} + +dependencies { + implementation(project(":nms:nms-common")) + + // Used for nms + paperweight.paperDevBundle("1.21.5-R0.1-SNAPSHOT") +} + +repositories { + maven("https://repo.papermc.io/repository/maven-public/") +} + +// Set target version +tasks.withType().configureEach { + sourceCompatibility = "21" + targetCompatibility = "21" + + options.encoding = "UTF-8" +} + +kotlin { + compilerOptions { + apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2) + jvmTarget.set(JvmTarget.JVM_21) + } +} diff --git a/nms/v1_21R4/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_21R4_PacketManager.kt b/nms/v1_21R4/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_21R4_PacketManager.kt new file mode 100644 index 0000000..b123625 --- /dev/null +++ b/nms/v1_21R4/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_21R4_PacketManager.kt @@ -0,0 +1,33 @@ +package xyz.alexcrea.cuanvil.dependency.packet.versions + +import net.minecraft.network.protocol.game.ClientboundPlayerAbilitiesPacket +import net.minecraft.world.entity.player.Abilities +import org.bukkit.craftbukkit.entity.CraftPlayer +import org.bukkit.entity.Player +import xyz.alexcrea.cuanvil.dependency.packet.PacketManager +import xyz.alexcrea.cuanvil.dependency.packet.PacketManagerBase + +class V1_21R4_PacketManager : PacketManagerBase(), PacketManager { + override val canSetInstantBuild: Boolean + get() = true + + override fun setInstantBuild(player: Player, instantBuild: Boolean) { + val nmsPlayer = (player as CraftPlayer).handle + val playerAbilities = nmsPlayer.abilities + val sendedAbilities: Abilities + if (playerAbilities.instabuild == instantBuild) { + sendedAbilities = playerAbilities + } else { + sendedAbilities = Abilities() + sendedAbilities.invulnerable = playerAbilities.invulnerable + sendedAbilities.flying = playerAbilities.flying + sendedAbilities.mayfly = playerAbilities.mayfly + sendedAbilities.instabuild = instantBuild + sendedAbilities.mayBuild = playerAbilities.mayBuild + sendedAbilities.flyingSpeed = playerAbilities.flyingSpeed + sendedAbilities.walkingSpeed = playerAbilities.walkingSpeed + } + val packet = ClientboundPlayerAbilitiesPacket(sendedAbilities) + nmsPlayer.connection.send(packet) + } +} diff --git a/nms/v1_21R5/.gitignore b/nms/v1_21R5/.gitignore new file mode 100644 index 0000000..47374f1 --- /dev/null +++ b/nms/v1_21R5/.gitignore @@ -0,0 +1 @@ +.lastDeploymentsId \ No newline at end of file diff --git a/nms/v1_21R5/build.gradle.kts b/nms/v1_21R5/build.gradle.kts new file mode 100644 index 0000000..e7f7c47 --- /dev/null +++ b/nms/v1_21R5/build.gradle.kts @@ -0,0 +1,34 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +group = rootProject.group +version = rootProject.version + +plugins { + id("io.papermc.paperweight.userdev") +} + +dependencies { + implementation(project(":nms:nms-common")) + + // Used for nms + paperweight.paperDevBundle("1.21.6-R0.1-SNAPSHOT") +} + +repositories { + maven("https://repo.papermc.io/repository/maven-public/") +} + +// Set target version +tasks.withType().configureEach { + sourceCompatibility = "21" + targetCompatibility = "21" + + options.encoding = "UTF-8" +} + +kotlin { + compilerOptions { + apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2) + jvmTarget.set(JvmTarget.JVM_21) + } +} diff --git a/nms/v1_21R5/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_21R5_PacketManager.kt b/nms/v1_21R5/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_21R5_PacketManager.kt new file mode 100644 index 0000000..561a6af --- /dev/null +++ b/nms/v1_21R5/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_21R5_PacketManager.kt @@ -0,0 +1,33 @@ +package xyz.alexcrea.cuanvil.dependency.packet.versions + +import net.minecraft.network.protocol.game.ClientboundPlayerAbilitiesPacket +import net.minecraft.world.entity.player.Abilities +import org.bukkit.craftbukkit.entity.CraftPlayer +import org.bukkit.entity.Player +import xyz.alexcrea.cuanvil.dependency.packet.PacketManager +import xyz.alexcrea.cuanvil.dependency.packet.PacketManagerBase + +class V1_21R5_PacketManager : PacketManagerBase(), PacketManager { + override val canSetInstantBuild: Boolean + get() = true + + override fun setInstantBuild(player: Player, instantBuild: Boolean) { + val nmsPlayer = (player as CraftPlayer).handle + val playerAbilities = nmsPlayer.abilities + val sendedAbilities: Abilities + if (playerAbilities.instabuild == instantBuild) { + sendedAbilities = playerAbilities + } else { + sendedAbilities = Abilities() + sendedAbilities.invulnerable = playerAbilities.invulnerable + sendedAbilities.flying = playerAbilities.flying + sendedAbilities.mayfly = playerAbilities.mayfly + sendedAbilities.instabuild = instantBuild + sendedAbilities.mayBuild = playerAbilities.mayBuild + sendedAbilities.flyingSpeed = playerAbilities.flyingSpeed + sendedAbilities.walkingSpeed = playerAbilities.walkingSpeed + } + val packet = ClientboundPlayerAbilitiesPacket(sendedAbilities) + nmsPlayer.connection.send(packet) + } +} diff --git a/nms/v1_21R6/.gitignore b/nms/v1_21R6/.gitignore new file mode 100644 index 0000000..47374f1 --- /dev/null +++ b/nms/v1_21R6/.gitignore @@ -0,0 +1 @@ +.lastDeploymentsId \ No newline at end of file diff --git a/nms/v1_21R6/build.gradle.kts b/nms/v1_21R6/build.gradle.kts new file mode 100644 index 0000000..3189b5c --- /dev/null +++ b/nms/v1_21R6/build.gradle.kts @@ -0,0 +1,34 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +group = rootProject.group +version = rootProject.version + +plugins { + id("io.papermc.paperweight.userdev") +} + +dependencies { + implementation(project(":nms:nms-common")) + + // Used for nms + paperweight.paperDevBundle("1.21.10-R0.1-SNAPSHOT") +} + +repositories { + maven("https://repo.papermc.io/repository/maven-public/") +} + +// Set target version +tasks.withType().configureEach { + sourceCompatibility = "21" + targetCompatibility = "21" + + options.encoding = "UTF-8" +} + +kotlin { + compilerOptions { + apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2) + jvmTarget.set(JvmTarget.JVM_21) + } +} diff --git a/nms/v1_21R6/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/PaperPacketManager.kt b/nms/v1_21R6/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/PaperPacketManager.kt new file mode 100644 index 0000000..ee00666 --- /dev/null +++ b/nms/v1_21R6/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/PaperPacketManager.kt @@ -0,0 +1,33 @@ +package xyz.alexcrea.cuanvil.dependency.packet.versions + +import net.minecraft.network.protocol.game.ClientboundPlayerAbilitiesPacket +import net.minecraft.world.entity.player.Abilities +import org.bukkit.craftbukkit.entity.CraftPlayer +import org.bukkit.entity.Player +import xyz.alexcrea.cuanvil.dependency.packet.PacketManager +import xyz.alexcrea.cuanvil.dependency.packet.PacketManagerBase + +class V1_21R6_PacketManager : PacketManagerBase(), PacketManager { + override val canSetInstantBuild: Boolean + get() = true + + override fun setInstantBuild(player: Player, instantBuild: Boolean) { + val nmsPlayer = (player as CraftPlayer).handle + val playerAbilities = nmsPlayer.abilities + val sendedAbilities: Abilities + if (playerAbilities.instabuild == instantBuild) { + sendedAbilities = playerAbilities + } else { + sendedAbilities = Abilities() + sendedAbilities.invulnerable = playerAbilities.invulnerable + sendedAbilities.flying = playerAbilities.flying + sendedAbilities.mayfly = playerAbilities.mayfly + sendedAbilities.instabuild = instantBuild + sendedAbilities.mayBuild = playerAbilities.mayBuild + sendedAbilities.flyingSpeed = playerAbilities.flyingSpeed + sendedAbilities.walkingSpeed = playerAbilities.walkingSpeed + } + val packet = ClientboundPlayerAbilitiesPacket(sendedAbilities) + nmsPlayer.connection.send(packet) + } +} \ No newline at end of file diff --git a/nms/v1_21R7/.gitignore b/nms/v1_21R7/.gitignore new file mode 100644 index 0000000..47374f1 --- /dev/null +++ b/nms/v1_21R7/.gitignore @@ -0,0 +1 @@ +.lastDeploymentsId \ No newline at end of file diff --git a/nms/v1_21R7/build.gradle.kts b/nms/v1_21R7/build.gradle.kts new file mode 100644 index 0000000..91c3111 --- /dev/null +++ b/nms/v1_21R7/build.gradle.kts @@ -0,0 +1,34 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +group = rootProject.group +version = rootProject.version + +plugins { + id("io.papermc.paperweight.userdev") +} + +dependencies { + implementation(project(":nms:nms-common")) + + // Used for nms + paperweight.paperDevBundle("1.21.11-R0.1-SNAPSHOT") +} + +repositories { + maven("https://repo.papermc.io/repository/maven-public/") +} + +// Set target version +tasks.withType().configureEach { + sourceCompatibility = "21" + targetCompatibility = "21" + + options.encoding = "UTF-8" +} + +kotlin { + compilerOptions { + apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2) + jvmTarget.set(JvmTarget.JVM_21) + } +} diff --git a/nms/v1_21R7/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_21R7_PacketManager.kt b/nms/v1_21R7/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_21R7_PacketManager.kt new file mode 100644 index 0000000..59ae9ce --- /dev/null +++ b/nms/v1_21R7/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_21R7_PacketManager.kt @@ -0,0 +1,33 @@ +package xyz.alexcrea.cuanvil.dependency.packet.versions + +import net.minecraft.network.protocol.game.ClientboundPlayerAbilitiesPacket +import net.minecraft.world.entity.player.Abilities +import org.bukkit.craftbukkit.entity.CraftPlayer +import org.bukkit.entity.Player +import xyz.alexcrea.cuanvil.dependency.packet.PacketManager +import xyz.alexcrea.cuanvil.dependency.packet.PacketManagerBase + +class V1_21R7_PacketManager : PacketManagerBase(), PacketManager { + override val canSetInstantBuild: Boolean + get() = true + + override fun setInstantBuild(player: Player, instantBuild: Boolean) { + val nmsPlayer = (player as CraftPlayer).handle + val playerAbilities = nmsPlayer.abilities + val sendedAbilities: Abilities + if (playerAbilities.instabuild == instantBuild) { + sendedAbilities = playerAbilities + } else { + sendedAbilities = Abilities() + sendedAbilities.invulnerable = playerAbilities.invulnerable + sendedAbilities.flying = playerAbilities.flying + sendedAbilities.mayfly = playerAbilities.mayfly + sendedAbilities.instabuild = instantBuild + sendedAbilities.mayBuild = playerAbilities.mayBuild + sendedAbilities.flyingSpeed = playerAbilities.flyingSpeed + sendedAbilities.walkingSpeed = playerAbilities.walkingSpeed + } + val packet = ClientboundPlayerAbilitiesPacket(sendedAbilities) + nmsPlayer.connection.send(packet) + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index bd7b34a..9de7d8c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -3,32 +3,20 @@ rootProject.name = "CustomAnvil" // NMS subproject include("nms:nms-common") findProject(":nms:nms-common")?.name = "nms-common" -include("nms:v1_17R1") -findProject(":nms:v1_17R1")?.name = "v1_17R1" -include("nms:v1_18R1") -findProject(":nms:v1_18R1")?.name = "v1_18R1" -include("nms:v1_18R2") -findProject(":nms:v1_18R2")?.name = "v1_18R2" -include("nms:v1_19R1") -findProject(":nms:v1_19R1")?.name = "v1_19R1" -include("nms:v1_19R2") -findProject(":nms:v1_19R2")?.name = "v1_19R2" -include("nms:v1_19R3") -findProject(":nms:v1_19R3")?.name = "v1_19R3" -include("nms:v1_20R1") -findProject(":nms:v1_20R1")?.name = "v1_20R1" -include("nms:v1_20R2") -findProject(":nms:v1_20R2")?.name = "v1_20R2" -include("nms:v1_20R3") -findProject(":nms:v1_20R3")?.name = "v1_20R3" -include("nms:v1_20R4") -findProject(":nms:v1_20R4")?.name = "v1_20R4" -include("nms:v1_21R1") -findProject(":nms:v1_21R1")?.name = "v1_21R1" -include("nms:v1_21R2") -findProject(":nms:v1_21R2")?.name = "v1_21R2" -include("nms:v1_21R3") -findProject(":nms:v1_21R3")?.name = "v1_21R3" +include("nms:nms-paper") +findProject(":nms:nms-paper")?.name = "nms-paper" + +val reobfNMS = providers.gradleProperty("subprojects.reobfnms") + .get().split(",") + +for (nmsPart in reobfNMS) { + include("nms:$nmsPart") + findProject(":nms:$nmsPart")?.name = nmsPart +} + +// compatibility subprojects include(":impl:LegacyEcoEnchant") -findProject(":impl:LegacyEcoEnchant")?.name = "LegacyEcoEnchant" \ No newline at end of file +findProject(":impl:LegacyEcoEnchant")?.name = "LegacyEcoEnchant" +include("impl:ExcellentEnchant5_4") +findProject(":impl:ExcellentEnchant5_4")?.name = "ExcellentEnchant5_4" \ No newline at end of file diff --git a/src/main/java/xyz/alexcrea/cuanvil/api/AnvilRecipeBuilder.java b/src/main/java/xyz/alexcrea/cuanvil/api/AnvilRecipeBuilder.java index 9d33bfb..4292fa0 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/api/AnvilRecipeBuilder.java +++ b/src/main/java/xyz/alexcrea/cuanvil/api/AnvilRecipeBuilder.java @@ -14,7 +14,10 @@ public class AnvilRecipeBuilder { private @NotNull String name; private boolean exactCount; - private int xpCostPerCraft; + private int levelCostPerCraft; + private int linearXpCostPerCraft; + + private boolean removeExactLinearXp; private @Nullable ItemStack leftItem; private @Nullable ItemStack rightItem; @@ -23,7 +26,7 @@ public class AnvilRecipeBuilder { /** * Instantiates a new Anvil recipe builder. * exact count default to true. - * xp cost per craft default to 1. + * xp level and linear cost per craft default to 0. * * @param name The recipe name */ @@ -31,7 +34,9 @@ public class AnvilRecipeBuilder { this.name = name; this.exactCount = true; - this.xpCostPerCraft = 1; + this.levelCostPerCraft = 0; + this.linearXpCostPerCraft = 0; + this.removeExactLinearXp = false; this.leftItem = null; this.rightItem = null; @@ -60,7 +65,7 @@ public class AnvilRecipeBuilder { } /** - * Get if the recipe is exact count. + * Get if the recipe is exact count. (default 0) *

* Exact count mean the recipe can only be crafted 1 by 1. * If set to false, then it will craft as much as possible in 1 go and will keep unused material onto the anvil inventory. @@ -86,12 +91,14 @@ public class AnvilRecipeBuilder { } /** - * Get the xp level cost per craft. + * Get the xp level cost per craft. (default 0) * * @return The xp level cost per craft + * @deprecated use {@link #getLevelCostPerCraft() getLevelCostPerCraft} instead */ + @Deprecated(since = "1.13.0") public int getXpCostPerCraft() { - return xpCostPerCraft; + return getLevelCostPerCraft(); } /** @@ -99,9 +106,78 @@ public class AnvilRecipeBuilder { * * @param xpCostPerCraft The xp level cost per craft * @return This recipe builder instance. + * @deprecated use {@link #setLevelCostPerCraft(int) setLevelCostPerCraft} instead */ + @Deprecated(since = "1.13.0") public AnvilRecipeBuilder setXpCostPerCraft(int xpCostPerCraft) { - this.xpCostPerCraft = xpCostPerCraft; + return setLevelCostPerCraft(xpCostPerCraft); + } + + /** + * Get the xp level cost per craft. (default 0) + * + * @return The xp level cost per craft + */ + public int getLevelCostPerCraft() { + return levelCostPerCraft; + } + + /** + * Sets the xp level cost per craft. + * + * @param levelCostPerCraft The xp level cost per craft + * @return This recipe builder instance. + */ + public AnvilRecipeBuilder setLevelCostPerCraft(int levelCostPerCraft) { + this.levelCostPerCraft = levelCostPerCraft; + return this; + } + + /** + * Get the linear xp cost (not xp level cost) per craft. + * + * @return The xp level cost per craft + */ + public int getLinearXpCostPerCraft() { + return linearXpCostPerCraft; + } + + /** + * Sets the linear xp cost (not xp level cost) per craft. + * + * @param linearXpCostPerCraft The linear xp cost per craft + * @return This recipe builder instance. + */ + public AnvilRecipeBuilder setLinearXpCostPerCraft(int linearXpCostPerCraft) { + this.linearXpCostPerCraft = linearXpCostPerCraft; + return this; + } + + /** + * Get if the linear xp should get removed by an exact amount. + *

+ * If false (default) level cost will be the level that would be reached by a player with this amount of xp. + * If true will require the level that has at least the specified level of xp then on click remove only the necessary xp + *

+ * linear xp cost are applied after level cost + * @return if we should remove the exact amount of linear xp + */ + public boolean isRemoveExactLinearXp() { + return removeExactLinearXp; + } + + /** + * Set if the linear xp should get removed by an exact amount. + *

+ * If false (default) level cost will be the level that would be reached by a player with this amount of xp. + * If true will require the level that has at least the specified level of xp then on click remove only the necessary xp + *

+ * linear xp cost are applied after level cost + * @param removeExactLinearXp if we should remove the exact amount of linear xp + * @return This recipe builder instance. + */ + public AnvilRecipeBuilder setRemoveExactLinearXp(boolean removeExactLinearXp) { + this.removeExactLinearXp = removeExactLinearXp; return this; } @@ -182,12 +258,14 @@ public class AnvilRecipeBuilder { */ @Nullable // null if missing argument public AnvilCustomRecipe build() { - if(leftItem == null || resultItem == null) return null; + if (leftItem == null || resultItem == null) return null; return new AnvilCustomRecipe( this.name, this.exactCount, - this.xpCostPerCraft, + this.levelCostPerCraft, + this.linearXpCostPerCraft, + this.removeExactLinearXp, this.leftItem, this.rightItem, this.resultItem ); } @@ -198,7 +276,7 @@ public class AnvilRecipeBuilder { * * @return True if successful. */ - public boolean registerIfAbsent(){ + public boolean registerIfAbsent() { return CustomAnvilRecipeApi.addRecipe(this); } diff --git a/src/main/java/xyz/alexcrea/cuanvil/api/ConflictBuilder.java b/src/main/java/xyz/alexcrea/cuanvil/api/ConflictBuilder.java index f662140..1460766 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/api/ConflictBuilder.java +++ b/src/main/java/xyz/alexcrea/cuanvil/api/ConflictBuilder.java @@ -13,6 +13,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +//TODO add conflict after level /** * A Builder for material conflict. */ diff --git a/src/main/java/xyz/alexcrea/cuanvil/api/EnchantmentApi.java b/src/main/java/xyz/alexcrea/cuanvil/api/EnchantmentApi.java index 76f1ac2..ac98225 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/api/EnchantmentApi.java +++ b/src/main/java/xyz/alexcrea/cuanvil/api/EnchantmentApi.java @@ -1,6 +1,7 @@ package xyz.alexcrea.cuanvil.api; import io.delilaheve.CustomAnvil; +import io.delilaheve.util.ConfigOptions; import org.bukkit.NamespacedKey; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.enchantments.Enchantment; @@ -180,13 +181,13 @@ public class EnchantmentApi { private static boolean tryWriteDefaultConfig(FileConfiguration defaultConfig, CAEnchantment enchantment, boolean override) { boolean hasChange = false; - String levelPath = "enchant_limits." + enchantment.getKey(); + String levelPath = ConfigOptions.ENCHANT_LIMIT_ROOT + "." + enchantment.getKey(); if(override || !defaultConfig.isSet(levelPath)){ defaultConfig.set(levelPath, enchantment.defaultMaxLevel()); hasChange = true; } - String basePath = "enchant_values." + enchantment.getKey(); + String basePath = ConfigOptions.ENCHANT_VALUES_ROOT + "." + enchantment.getKey(); EnchantmentRarity rarity = enchantment.defaultRarity(); String itemPath = basePath + ".item"; diff --git a/src/main/java/xyz/alexcrea/cuanvil/api/MaterialGroupApi.java b/src/main/java/xyz/alexcrea/cuanvil/api/MaterialGroupApi.java index 250f797..cd71c7a 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/api/MaterialGroupApi.java +++ b/src/main/java/xyz/alexcrea/cuanvil/api/MaterialGroupApi.java @@ -3,6 +3,7 @@ package xyz.alexcrea.cuanvil.api; import io.delilaheve.CustomAnvil; import io.delilaheve.util.ConfigOptions; import org.bukkit.Material; +import org.bukkit.NamespacedKey; import org.bukkit.configuration.file.FileConfiguration; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -105,7 +106,9 @@ public class MaterialGroupApi { if (group instanceof IncludeGroup includeGroup) { changed = writeKnownGroup("include", includeGroup); } else if (group instanceof ExcludeGroup excludeGroup) { - changed = writeKnownGroup("exclude", excludeGroup); + throw new UnsupportedOperationException("exclude group is temporarily disable for the time being. sorry"); + // This code do not do what is intended ? idk why do it exist + //changed = writeKnownGroup("exclude", excludeGroup); } else { changed = writeUnknownGroup(group); } @@ -121,16 +124,27 @@ public class MaterialGroupApi { FileConfiguration config = ConfigHolder.ITEM_GROUP_HOLDER.getConfig(); String basePath = group.getName() + "."; - Set materialSet = group.getNonGroupInheritedMaterials(); + Set materialSet = group.getNonGroupInheritedMaterials(); Set groupSet = group.getGroups(); + boolean empty = true; if (!materialSet.isEmpty()) { config.set(basePath + ItemGroupManager.MATERIAL_LIST_PATH, materialSetToStringList(materialSet)); + empty = false; + } else { + config.set(basePath + ItemGroupManager.MATERIAL_LIST_PATH, null); } if (!groupSet.isEmpty()) { config.set(basePath + ItemGroupManager.GROUP_LIST_PATH, materialGroupSetToStringList(groupSet)); + empty = false; + } else { + config.set(basePath + ItemGroupManager.GROUP_LIST_PATH, null); + } + + if (empty) { + config.set(basePath + ItemGroupManager.GROUP_TYPE_PATH, null); + return false; } - if (!config.isConfigurationSection(group.getName())) return false; config.set(basePath + ItemGroupManager.GROUP_TYPE_PATH, groupType); return true; @@ -140,7 +154,7 @@ public class MaterialGroupApi { FileConfiguration config = ConfigHolder.ITEM_GROUP_HOLDER.getConfig(); String basePath = group.getName() + "."; - EnumSet materials = group.getMaterials(); + Set materials = group.getMaterials(); if (materials.isEmpty()) return false; @@ -150,8 +164,8 @@ public class MaterialGroupApi { return true; } - public static List materialSetToStringList(@NotNull Set materials) { - return materials.stream().map(material -> material.getKey().getKey().toLowerCase()).toList(); + public static List materialSetToStringList(@NotNull Set materials) { + return materials.stream().map(NamespacedKey::toString).toList(); } public static List materialGroupSetToStringList(@NotNull Set groups) { diff --git a/src/main/java/xyz/alexcrea/cuanvil/api/event/CAConfigReadyEvent.java b/src/main/java/xyz/alexcrea/cuanvil/api/event/CAConfigReadyEvent.java index 24691db..67d27a8 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/api/event/CAConfigReadyEvent.java +++ b/src/main/java/xyz/alexcrea/cuanvil/api/event/CAConfigReadyEvent.java @@ -3,6 +3,23 @@ package xyz.alexcrea.cuanvil.api.event; import org.bukkit.event.Event; import org.bukkit.event.HandlerList; +/** + * Called when the configuration of CustomAnvil is ready. + * It is called either on the plugin startup or on the plugin config reload. + *

+ * If you want to listen to the first trigger of this event (first configuration load. aka plugin load) + * you will need to register the listener on your plugin onEnable or earlier + *

+ * This event indicate that can start to register your recipes, item groups and conflicts. + * The vanilla and custom enchantments should already have been provided to CustomAnvil. + * Configuration can be changed any time after this event is triggered but never before. + *

+ * use {@link xyz.alexcrea.cuanvil.api.ConflictAPI ConflictApi}, + * {@link xyz.alexcrea.cuanvil.gui.config.global.CustomRecipeConfigGui CustomRecipeConfigGui}, + * {@link xyz.alexcrea.cuanvil.api.MaterialGroupApi MaterialGroupApi} + * and {@link xyz.alexcrea.cuanvil.api.UnitRepairApi UnitRepairApi} + * to add/remove/edit configurations + */ public class CAConfigReadyEvent extends Event { private static final HandlerList HANDLERS = new HandlerList(); diff --git a/src/main/java/xyz/alexcrea/cuanvil/api/event/CAEnchantRegistryReadyEvent.java b/src/main/java/xyz/alexcrea/cuanvil/api/event/CAEnchantRegistryReadyEvent.java index 3e2fdf8..3ffe372 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/api/event/CAEnchantRegistryReadyEvent.java +++ b/src/main/java/xyz/alexcrea/cuanvil/api/event/CAEnchantRegistryReadyEvent.java @@ -3,6 +3,17 @@ package xyz.alexcrea.cuanvil.api.event; import org.bukkit.event.Event; import org.bukkit.event.HandlerList; +/** + * Called when custom anvil is ready to accept registration on custom enchantment. + *

+ * If you want to listen this event + * you will need to register the listener on your plugin onEnable or earlier + *

+ * Custom enchantments may be registered later but may cause issue if registered too later + * (after configuration loading phase. see {@link CAConfigReadyEvent}) + *

+ * use {@link xyz.alexcrea.cuanvil.api.EnchantmentApi EnchantmentApi} to register and unregister your custom enchantments + */ public class CAEnchantRegistryReadyEvent extends Event { private static final HandlerList HANDLERS = new HandlerList(); diff --git a/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CAClickResultBypassEvent.java b/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CAClickResultBypassEvent.java new file mode 100644 index 0000000..fe5e199 --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CAClickResultBypassEvent.java @@ -0,0 +1,63 @@ +package xyz.alexcrea.cuanvil.api.event.listener; + +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.jetbrains.annotations.NotNull; + +/** + * Called before custom anvil process the click on the result on the anvil inventory. + *

+ * This event is called after checking that the inventory is an anvil inventory and that the click is on the result slot + * but before checking if the player has the custom anvil affected permission. + *

+ * This event being cancelled will make CustomAnvil abort the click on result process. + *

+ * Most of the time you would likely need {@link CAPreAnvilBypassEvent} or {@link CAEarlyPreAnvilBypassEvent} + * for this event to be useful. + *

+ * There is also {@link CATreatAnvilResult2Event} that may be better for some use case. + */ +public class CAClickResultBypassEvent extends Event implements Cancellable { + + private static final HandlerList HANDLERS = new HandlerList(); + + public static HandlerList getHandlerList() { + return HANDLERS; + } + + @Override + public @NotNull HandlerList getHandlers() { + return HANDLERS; + } + + private boolean cancelled = false; + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @NotNull + private final InventoryClickEvent event; + + /** + * Get the bukkit inventory click event causing to this event + * + * @return The click event causing to this event + */ + @NotNull + public InventoryClickEvent getEvent() { + return event; + } + + public CAClickResultBypassEvent(@NotNull InventoryClickEvent event) { + this.event = event; + } +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CAEarlyPreAnvilBypassEvent.java b/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CAEarlyPreAnvilBypassEvent.java new file mode 100644 index 0000000..e92b4cd --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CAEarlyPreAnvilBypassEvent.java @@ -0,0 +1,63 @@ +package xyz.alexcrea.cuanvil.api.event.listener; + +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.bukkit.event.inventory.PrepareAnvilEvent; +import org.jetbrains.annotations.NotNull; + +/** + * Called before custom anvil process the prepare anvil event. + *

+ * This event will always get called when CustomAnvil need to handle + *

+ * This event being cancelled will make CustomAnvil abort the anvil process. + *

+ * You should also use {@link CAClickResultBypassEvent} if you want to use this event for something useful. + *

+ * It is also recommended that you read about {@link CAPreAnvilBypassEvent} and {@link CATreatAnvilResult2Event} + * as your use case may be more prone to use theses. + */ +public class CAEarlyPreAnvilBypassEvent extends Event implements Cancellable { + + private static final HandlerList HANDLERS = new HandlerList(); + + public static HandlerList getHandlerList() { + return HANDLERS; + } + + @Override + public @NotNull HandlerList getHandlers() { + return HANDLERS; + } + + private boolean cancelled = false; + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @NotNull + private final PrepareAnvilEvent event; + + /** + * Get the bukkit pre anvil event causing this event + * + * @return The pre anvil event causing to this event + */ + @NotNull + public PrepareAnvilEvent getEvent() { + return event; + } + + public CAEarlyPreAnvilBypassEvent(@NotNull PrepareAnvilEvent event) { + this.event = event; + } + +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CAPreAnvilBypassEvent.java b/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CAPreAnvilBypassEvent.java new file mode 100644 index 0000000..9103a4b --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CAPreAnvilBypassEvent.java @@ -0,0 +1,66 @@ +package xyz.alexcrea.cuanvil.api.event.listener; + +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.bukkit.event.inventory.PrepareAnvilEvent; +import org.jetbrains.annotations.NotNull; + +/** + * Called before custom anvil process the prepare anvil event. + *

+ * This event is called after {@link CAEarlyPreAnvilBypassEvent}, + * after checking that there is at least an item on the left slot + * and after checking if any of the 2 item is marked as immutable + * but before checking if the player has the custom anvil affected permission. + *

+ * This event being cancelled will make CustomAnvil abort the anvil process. + *

+ * You should also use {@link CAClickResultBypassEvent} if you want to use this event for something useful. + *

+ * It is also recommended that you read about {@link CAEarlyPreAnvilBypassEvent} and {@link CATreatAnvilResult2Event} + * as your use case may be more prone to use theses. + */ +public class CAPreAnvilBypassEvent extends Event implements Cancellable { + + private static final HandlerList HANDLERS = new HandlerList(); + + public static HandlerList getHandlerList() { + return HANDLERS; + } + + @Override + public @NotNull HandlerList getHandlers() { + return HANDLERS; + } + + private boolean cancelled = false; + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @NotNull + private final PrepareAnvilEvent event; + + /** + * Get the bukkit pre anvil event causing this event + * + * @return The pre anvil event causing this event + */ + @NotNull + public PrepareAnvilEvent getEvent() { + return event; + } + + public CAPreAnvilBypassEvent(@NotNull PrepareAnvilEvent event) { + this.event = event; + } + +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CATreatAnvilResult2Event.java b/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CATreatAnvilResult2Event.java new file mode 100644 index 0000000..30c5380 --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CATreatAnvilResult2Event.java @@ -0,0 +1,196 @@ +package xyz.alexcrea.cuanvil.api.event.listener; + +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import xyz.alexcrea.cuanvil.anvil.AnvilCost; +import xyz.alexcrea.cuanvil.anvil.AnvilUseType; + +/** + * Called after custom anvil processed the click on the result on the anvil inventory. + * This event should be used to modify the result of an anvil use. + *

+ * You may also want to check {@link CAClickResultBypassEvent}, + * {@link CAPreAnvilBypassEvent} + * and {@link CAEarlyPreAnvilBypassEvent} for your use case + *

+ * A null result will cancel this event + */ +@SuppressWarnings("unused") +public class CATreatAnvilResult2Event extends Event { + + private static final HandlerList HANDLERS = new HandlerList(); + + public static HandlerList getHandlerList() { + return HANDLERS; + } + + @Override + public @NotNull HandlerList getHandlers() { + return HANDLERS; + } + + @NotNull + private final InventoryView view; + + private final AnvilUseType useType; + + @Nullable + private final ItemStack left; + @Nullable + private final ItemStack right; + + @Nullable + private ItemStack result; + + private final AnvilCost cost; + + @ApiStatus.Internal + public CATreatAnvilResult2Event( + @NotNull InventoryView view, + Inventory inv, + AnvilUseType useType, + @Nullable ItemStack result, + AnvilCost cost) { + this.view = view; + this.useType = useType; + + this.left = inv.getItem(0); // TODO use view here + this.right = inv.getItem(1); + this.result = result; + this.cost = cost; + } + + /** + * Get the bukkit inventory view. + *

+ * Temporarily marked as internal as it will get changed to anvil view on legacy removal + * so signature will change + * + * @return The inventory view of this event. + */ + @ApiStatus.Internal + public @NotNull InventoryView getView() { + return view; + } + + + /** + * Get the type of use source of the result. + * + * @return The craft use type. + */ + public AnvilUseType getUseType() { + return useType; + } + + /** + * Get the left item of the anvil use + * + * @return the left item + */ + public @Nullable ItemStack getLeftItem() { + return left; + } + + /** + * Get the right item of the anvil use + * + * @return the right item + */ + public @Nullable ItemStack getRightItem() { + return right; + } + + /** + * Get the current result + *

+ * note that it will not be null unless another listener previously set it to null. + * + * @return The current result. + */ + public @Nullable ItemStack getResult() { + return result; + } + + /** + * Set the current result + *

+ * note that a null result will cancel this anvil use. + * + * @param result The new result + */ + public void setResult(@Nullable ItemStack result) { + this.result = result; + } + + /** + * Get the level cost displayed on the anvil. + *

Important note:

+ * the final price are re calculated on click for the following use case: + *
    + *
  • Custom craft
  • + *
  • Unit repair
  • + *
  • Lore edit
  • + *
+ * This value will be used as final price for: + *
  • Item merge
  • + *
  • Item rename
  • + * + * + * @return The current cost. + * @deprecated use #{@link #getCost()} instead + */ + @Deprecated(forRemoval = true, since = "1.17.0") + public int getLevelCost() { + return cost.asXpCost(); + } + + /** + * Set the level cost displayed on the anvil. + *

    Important note:

    + * the final price are re calculated on click for the following use case: + *
      + *
    • Custom craft
    • + *
    • Unit repair
    • + *
    • Lore edit
    • + *
    + * This value will be used as final price for: + *
  • Item merge
  • + *
  • Item rename
  • + * + * + * @param levelCost The new cost. + * @deprecated use #{@link #getCost()} and set value on this instead + */ + @Deprecated(forRemoval = true, since = "1.17.0") + public void setLevelCost(int levelCost) { + cost.setGeneric(levelCost - cost.getGeneric() - cost.asXpCost()); + } + + /** + * Allow access to the current cost of the event + * Note that modifying this object will change the event resulting cost + * + *

    Important note:

    + * the final price are re calculated on click for the following use case: + *
      + *
    • Custom craft
    • + *
    • Unit repair
    • + *
    • Lore edit
    • + *
    + * This value will be used as final price for: + *
  • Item merge
  • + *
  • Item rename
  • + * + * @return the current anvil cost + */ + public AnvilCost getCost() { + return cost; + } +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CATreatAnvilResultEvent.java b/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CATreatAnvilResultEvent.java new file mode 100644 index 0000000..80965b5 --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CATreatAnvilResultEvent.java @@ -0,0 +1,162 @@ +package xyz.alexcrea.cuanvil.api.event.listener; + +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.bukkit.event.inventory.PrepareAnvilEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import xyz.alexcrea.cuanvil.anvil.AnvilCost; +import xyz.alexcrea.cuanvil.anvil.AnvilUseType; + +/** + * Called after custom anvil processed the click on the result on the anvil inventory. + * This event should be used to modify the result of an anvil use. + *

    + * You may also want to check {@link CAClickResultBypassEvent}, + * {@link CAPreAnvilBypassEvent} + * and {@link CAEarlyPreAnvilBypassEvent} for your use case + *

    + * A null result will cancel this pre anvil event + * + * @deprecated Prepare anvil Event cannot be provided as it can be called on result and therefore not have prepared anvil event + * use {@link CATreatAnvilResult2Event} instead + */ +@SuppressWarnings("unused") +@Deprecated(forRemoval = true, since = "1.17.0") +public class CATreatAnvilResultEvent extends Event { + + private static final HandlerList HANDLERS = new HandlerList(); + + public static HandlerList getHandlerList() { + return HANDLERS; + } + + @Override + public @NotNull HandlerList getHandlers() { + return HANDLERS; + } + + @NotNull + private final PrepareAnvilEvent event; + + private final AnvilUseType useType; + + @Nullable + private ItemStack result; + + private final AnvilCost cost; + + public CATreatAnvilResultEvent(@NotNull PrepareAnvilEvent event, AnvilUseType useType, @Nullable ItemStack result, AnvilCost cost) { + this.event = event; + this.useType = useType; + this.result = result; + this.cost = cost; + } + + /** + * Get the bukkit inventory click event causing to this event. + * + * @return The click event causing to this event. + */ + public @NotNull PrepareAnvilEvent getEvent() { + return event; + } + + /** + * Get the type of use source of the result. + * + * @return The craft use type. + */ + public AnvilUseType getUseType() { + return useType; + } + + /** + * Get the current result + *

    + * note that it will not be null unless another listener previously set it to null. + * + * @return The current result. + */ + public @Nullable ItemStack getResult() { + return result; + } + + /** + * Set the current result + *

    + * note that a null result will cancel this anvil use. + * + * @param result The new result + */ + public void setResult(@Nullable ItemStack result) { + this.result = result; + } + + /** + * Get the level cost displayed on the anvil. + *

    Important note:

    + * the final price are re calculated on click for the following use case: + *
      + *
    • Custom craft
    • + *
    • Unit repair
    • + *
    • Lore edit
    • + *
    + * This value will be used as final price for: + *
  • Item merge
  • + *
  • Item rename
  • + * + * + * @return The current cost. + * @deprecated use #{@link #getCost()} instead + */ + @Deprecated(forRemoval = true, since = "1.17.0") + public int getLevelCost() { + return cost.asXpCost(); + } + + /** + * Set the level cost displayed on the anvil. + *

    Important note:

    + * the final price are re calculated on click for the following use case: + *
      + *
    • Custom craft
    • + *
    • Unit repair
    • + *
    • Lore edit
    • + *
    + * This value will be used as final price for: + *
  • Item merge
  • + *
  • Item rename
  • + * + * + * @param levelCost The new cost. + * @deprecated use #{@link #getCost()} and set value on this instead + */ + @Deprecated(forRemoval = true, since = "1.17.0") + public void setLevelCost(int levelCost) { + cost.setGeneric(levelCost - cost.getGeneric() - cost.asXpCost()); + } + + /** + * Allow access to the current cost of the event + * Note that modifying this object will change the event resulting cost + * + *

    Important note:

    + * the final price are re calculated on click for the following use case: + *
      + *
    • Custom craft
    • + *
    • Unit repair
    • + *
    • Lore edit
    • + *
    + * This value will be used as final price for: + *
  • Item merge
  • + *
  • Item rename
  • + * + * @return the current anvil cost + */ + public AnvilCost getCost() { + return cost; + } + +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/config/ConfigHolder.java b/src/main/java/xyz/alexcrea/cuanvil/config/ConfigHolder.java index 2037e23..f6a7e80 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/config/ConfigHolder.java +++ b/src/main/java/xyz/alexcrea/cuanvil/config/ConfigHolder.java @@ -9,6 +9,7 @@ import org.jetbrains.annotations.Nullable; import xyz.alexcrea.cuanvil.group.EnchantConflictManager; import xyz.alexcrea.cuanvil.group.ItemGroupManager; import xyz.alexcrea.cuanvil.recipe.CustomAnvilRecipeManager; +import xyz.alexcrea.cuanvil.util.MetricsUtil; import java.io.File; import java.io.IOException; @@ -145,6 +146,7 @@ public abstract class ConfigHolder { sufficientSuccess = true; } catch (IOException e) { CustomAnvil.instance.getLogger().log(Level.WARNING, "Could not copy backup saving config " + base.getName(), e); + MetricsUtil.INSTANCE.trackError(e); } } // save last backup @@ -275,6 +277,7 @@ public abstract class ConfigHolder { this.deletedConfigFile.createNewFile(); } catch (IOException e) { CustomAnvil.instance.getLogger().log(Level.WARNING, "Could not create " + this.deletedConfigFile.getPath(), e); + MetricsUtil.INSTANCE.trackError(e); } loadDeletedListFile(false); @@ -312,6 +315,7 @@ public abstract class ConfigHolder { this.deletedListConfig.save(this.deletedConfigFile); } catch (IOException e) { CustomAnvil.instance.getLogger().log(Level.WARNING, "Could not save " + this.deletedConfigFile.getPath(), e); + MetricsUtil.INSTANCE.trackError(e); return false; } diff --git a/src/main/java/xyz/alexcrea/cuanvil/config/WorkPenaltyType.java b/src/main/java/xyz/alexcrea/cuanvil/config/WorkPenaltyType.java index 75b0861..d374999 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/config/WorkPenaltyType.java +++ b/src/main/java/xyz/alexcrea/cuanvil/config/WorkPenaltyType.java @@ -2,7 +2,7 @@ package xyz.alexcrea.cuanvil.config; import com.google.common.collect.ImmutableMap; import org.jetbrains.annotations.Nullable; -import xyz.alexcrea.cuanvil.util.AnvilUseType; +import xyz.alexcrea.cuanvil.anvil.AnvilUseType; import java.util.EnumMap; diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/AdditionalTestEnchantment.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/AdditionalTestEnchantment.java index 832e5af..821838f 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/enchant/AdditionalTestEnchantment.java +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/AdditionalTestEnchantment.java @@ -1,6 +1,7 @@ package xyz.alexcrea.cuanvil.enchant; import org.bukkit.Material; +import org.bukkit.NamespacedKey; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; @@ -11,24 +12,23 @@ public interface AdditionalTestEnchantment { /** * Test if the provided enchantments can be compatible with this enchantment. only non-Custom Anvil conflict. * @param enchantments Immutable map of validated enchantments for the item. - * @param itemMat Material of the tested item. + * @param itemType Material namespaced key of the tested item. * @return If there is a conflict with the enchantments. */ boolean isEnchantConflict( @NotNull Map enchantments, - @NotNull Material itemMat); - + @NotNull NamespacedKey itemType); /** * Test if the provided item can be compatible with this enchantment. only non-Custom Anvil conflict. * @param enchantments Immutable map of validated enchantments for the item. - * @param itemMat Material of the tested item. + * @param itemType Material namespaced key of the tested item. * @param item Provide a new instance of the used item stack with the partial enchantment applied. * @return If there is a conflict with the enchantment and the item. */ boolean isItemConflict( @NotNull Map enchantments, - @NotNull Material itemMat, + @NotNull NamespacedKey itemType, @NotNull ItemStack item); } diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/CAEnchantment.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/CAEnchantment.java index 1d94ba2..ea657ac 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/enchant/CAEnchantment.java +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/CAEnchantment.java @@ -207,7 +207,6 @@ public interface CAEnchantment { @NotNull ItemMeta meta, @NotNull Map enchantments, @NotNull Collection enchantmentToTest){ - for (CAEnchantment enchantment : enchantmentToTest) { if(enchantment.isEnchantmentPresent(item, meta)){ enchantments.put(enchantment, enchantment.getLevel(item, meta)); diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/CAEnchantmentRegistry.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/CAEnchantmentRegistry.java index 5e2b1c1..854ed55 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/enchant/CAEnchantmentRegistry.java +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/CAEnchantmentRegistry.java @@ -10,6 +10,7 @@ import xyz.alexcrea.cuanvil.enchant.bulk.BukkitEnchantBulkOperation; import xyz.alexcrea.cuanvil.enchant.bulk.BulkCleanEnchantOperation; import xyz.alexcrea.cuanvil.enchant.bulk.BulkGetEnchantOperation; import xyz.alexcrea.cuanvil.enchant.wrapped.CABukkitEnchantment; +import xyz.alexcrea.cuanvil.util.MetricsUtil; import java.util.*; import java.util.logging.Level; @@ -76,20 +77,22 @@ public class CAEnchantmentRegistry { */ public boolean register(@NotNull CAEnchantment enchantment) { if (byKeyMap.containsKey(enchantment.getKey())) { - if (!enchantment.equals(byKeyMap.get(enchantment.getKey()))) { + if (Objects.equals(enchantment, byKeyMap.get(enchantment.getKey()))) { // We are trying to register the exact same enchantment. so we just skip it. return false; } - if(ConfigHolder.DEFAULT_CONFIG.getConfig().getBoolean("caution_secret_do_not_log_duplicated_registered_key", false)){ + if (ConfigHolder.DEFAULT_CONFIG.getConfig().getBoolean("caution_secret_do_not_log_duplicated_registered_key", false)) { return false; } + var error = new IllegalStateException("enchantment " + enchantment.getKey() + " was already registered"); CustomAnvil.instance.getLogger().log(Level.WARNING, "Duplicate distinct registered enchantment. This should NOT happen any time.\n" + "If you are a custom anvil developer: Maybe custom anvil detected your enchantment as a bukkit enchantment. " + "you should maybe remove enchantment with the same key before registering yours", - new IllegalStateException("enchantment " + enchantment.getKey() + " was already registered")); + error); + MetricsUtil.INSTANCE.trackError(error); return false; } diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/bulk/BukkitEnchantBulkOperation.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/bulk/BukkitEnchantBulkOperation.java index c5bd07d..73e4185 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/enchant/bulk/BukkitEnchantBulkOperation.java +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/bulk/BukkitEnchantBulkOperation.java @@ -1,6 +1,7 @@ package xyz.alexcrea.cuanvil.enchant.bulk; import io.delilaheve.CustomAnvil; +import io.delilaheve.util.ConfigOptions; import io.delilaheve.util.ItemUtil; import org.bukkit.Material; import org.bukkit.enchantments.Enchantment; @@ -17,11 +18,14 @@ public class BukkitEnchantBulkOperation implements BulkGetEnchantOperation, Bulk @Override public void bulkGet(@NotNull Map enchantmentMap, @NotNull ItemStack item, @NotNull ItemMeta meta) { - if (ItemUtil.INSTANCE.isEnchantedBook(item)) { + boolean isBook = ItemUtil.INSTANCE.isEnchantedBook(item); + + if (isBook) { ((EnchantmentStorageMeta) meta).getStoredEnchants().forEach((enchantment, level) -> addEnchantment(enchantmentMap, enchantment, level) ); - } else { + } + if(!isBook || ConfigOptions.INSTANCE.getAddBookEnchantmentAsStoredEnchantment()){ item.getEnchantments().forEach((enchantment, level) -> addEnchantment(enchantmentMap, enchantment, level) ); @@ -41,7 +45,7 @@ public class BukkitEnchantBulkOperation implements BulkGetEnchantOperation, Bulk @Override public void bulkClear(@NotNull ItemStack item) { - if (item.getType() != Material.ENCHANTED_BOOK) { + if (item.getType() != Material.ENCHANTED_BOOK || ConfigOptions.INSTANCE.getAddBookEnchantmentAsStoredEnchantment()) { item.getEnchantments().forEach((enchantment, level) -> item.removeEnchantment(enchantment) diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/bulk/EnchantSquaredBulkOperation.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/bulk/EnchantSquaredBulkOperation.java index ed4d010..57ecf60 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/enchant/bulk/EnchantSquaredBulkOperation.java +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/bulk/EnchantSquaredBulkOperation.java @@ -5,7 +5,7 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.jetbrains.annotations.NotNull; import xyz.alexcrea.cuanvil.dependency.DependencyManager; -import xyz.alexcrea.cuanvil.dependency.EnchantmentSquaredDependency; +import xyz.alexcrea.cuanvil.dependency.plugins.EnchantmentSquaredDependency; import xyz.alexcrea.cuanvil.enchant.CAEnchantment; import java.util.Collections; diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/bulk/SuperEnchantBulkOperation.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/bulk/SuperEnchantBulkOperation.java new file mode 100644 index 0000000..8bc729a --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/bulk/SuperEnchantBulkOperation.java @@ -0,0 +1,47 @@ +package xyz.alexcrea.cuanvil.enchant.bulk; + +import com.maddoxh.superEnchants.items.EnchantApplicator; +import com.maddoxh.superEnchants.items.EnchantReader; +import io.delilaheve.CustomAnvil; +import org.bukkit.NamespacedKey; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; +import xyz.alexcrea.cuanvil.api.EnchantmentApi; +import xyz.alexcrea.cuanvil.enchant.CAEnchantment; + +import java.util.Map; + +public class SuperEnchantBulkOperation implements BulkGetEnchantOperation, BulkCleanEnchantOperation { + + private Plugin plugin; + public SuperEnchantBulkOperation(Plugin plugin) { + this.plugin = plugin; + } + + @Override + public void bulkGet(@NotNull Map enchantmentMap, @NotNull ItemStack item, @NotNull ItemMeta meta) { + EnchantReader.INSTANCE.readEnchants(item).forEach((ench, level) -> { + var enchantment = EnchantmentApi.getByKey(NamespacedKey.fromString(ench, plugin)); + if(enchantment == null) { + CustomAnvil.log("Enchantment " + ench + " not found in custom anvil"); + return; + } + + enchantmentMap.put(enchantment, level); + } + ); + } + + @Override + public void bulkClear(@NotNull ItemStack item) { + EnchantApplicator.INSTANCE.clearAllCustomEnchants(item); + } + + @Override + public void bulkClear(@NotNull ItemStack item, @NotNull ItemMeta meta) { + // item meta is not preferred for enchantment squared clear + } + +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CABukkitEnchantment.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CABukkitEnchantment.java index 585c276..0e630ea 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CABukkitEnchantment.java +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CABukkitEnchantment.java @@ -1,6 +1,7 @@ package xyz.alexcrea.cuanvil.enchant.wrapped; import io.delilaheve.CustomAnvil; +import io.delilaheve.util.ConfigOptions; import io.delilaheve.util.ItemUtil; import org.bukkit.enchantments.Enchantment; import org.bukkit.enchantments.EnchantmentTarget; @@ -18,6 +19,7 @@ import java.lang.reflect.Method; import java.util.HashMap; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.logging.Level; /** @@ -25,17 +27,17 @@ import java.util.logging.Level; */ public class CABukkitEnchantment extends CAEnchantmentBase { - private final @NotNull Enchantment enchantment; + public final @NotNull Enchantment bukkit; - public CABukkitEnchantment(@NotNull Enchantment enchantment, @Nullable EnchantmentRarity rarity){ - super(enchantment.getKey(), + public CABukkitEnchantment(@NotNull Enchantment bukkit, @Nullable EnchantmentRarity rarity) { + super(bukkit.getKey(), rarity, - enchantment.getMaxLevel()); - this.enchantment = enchantment; + bukkit.getMaxLevel()); + this.bukkit = bukkit; } - public CABukkitEnchantment(@NotNull Enchantment enchantment){ - this(enchantment, getRarity(enchantment)); + public CABukkitEnchantment(@NotNull Enchantment bukkit) { + this(bukkit, getRarity(bukkit)); } @Override @@ -51,33 +53,34 @@ public class CABukkitEnchantment extends CAEnchantmentBase { @Override public int getLevel(@NotNull ItemStack item, @NotNull ItemMeta meta) { if (ItemUtil.INSTANCE.isEnchantedBook(item)) { - return ((EnchantmentStorageMeta)meta).getStoredEnchantLevel(this.enchantment); + return ((EnchantmentStorageMeta) meta).getStoredEnchantLevel(this.bukkit); } else { - return meta.getEnchantLevel(this.enchantment); + return meta.getEnchantLevel(this.bukkit); } } @Override public boolean isEnchantmentPresent(@NotNull ItemStack item, @NotNull ItemMeta meta) { if (ItemUtil.INSTANCE.isEnchantedBook(item)) { - EnchantmentStorageMeta bookMeta = ((EnchantmentStorageMeta)meta); + EnchantmentStorageMeta bookMeta = ((EnchantmentStorageMeta) meta); - return bookMeta.getStoredEnchants().containsKey(this.enchantment); - }else{ - return item.containsEnchantment(this.enchantment); + return bookMeta.getStoredEnchants().containsKey(this.bukkit) || + (ConfigOptions.INSTANCE.getAddBookEnchantmentAsStoredEnchantment() && item.containsEnchantment(this.bukkit)); + } else { + return item.containsEnchantment(this.bukkit); } } @Override public void addEnchantmentUnsafe(@NotNull ItemStack item, int level) { if (ItemUtil.INSTANCE.isEnchantedBook(item)) { - EnchantmentStorageMeta bookMeta = ((EnchantmentStorageMeta)item.getItemMeta()); + EnchantmentStorageMeta bookMeta = ((EnchantmentStorageMeta) item.getItemMeta()); assert bookMeta != null; - bookMeta.addStoredEnchant(this.enchantment, level, true); + bookMeta.addStoredEnchant(this.bukkit, level, true); item.setItemMeta(bookMeta); } else { - item.addUnsafeEnchantment(this.enchantment, level); + item.addUnsafeEnchantment(this.bukkit, level); } } @@ -85,19 +88,20 @@ public class CABukkitEnchantment extends CAEnchantmentBase { @Override public void removeFrom(@NotNull ItemStack item) { if (ItemUtil.INSTANCE.isEnchantedBook(item)) { - EnchantmentStorageMeta bookMeta = ((EnchantmentStorageMeta)item.getItemMeta()); + EnchantmentStorageMeta bookMeta = ((EnchantmentStorageMeta) item.getItemMeta()); assert bookMeta != null; - bookMeta.removeStoredEnchant(this.enchantment); + bookMeta.removeStoredEnchant(this.bukkit); + bookMeta.removeEnchant(this.bukkit); item.setItemMeta(bookMeta); - }else{ - item.removeEnchantment(this.enchantment); + } else { + item.removeEnchantment(this.bukkit); } } @NotNull - public static EnchantmentRarity getRarity(Enchantment enchantment){ + public static EnchantmentRarity getRarity(Enchantment enchantment) { try { return EnchantmentProperties.valueOf(enchantment.getKey().getKey().toUpperCase(Locale.ENGLISH)).getRarity(); } catch (IllegalArgumentException ignored) { @@ -107,10 +111,11 @@ public class CABukkitEnchantment extends CAEnchantmentBase { @NotNull protected Enchantment getEnchant() { - return this.enchantment; + return this.bukkit; } private static Method getAnvilCostMethod; + static { Class clazz = Enchantment.class; try { @@ -143,14 +148,14 @@ public class CABukkitEnchantment extends CAEnchantmentBase { } private static EnchantmentRarity findRarity(Enchantment enchantment) { - if(getAnvilCostMethod == null) return EnchantmentRarity.COMMON; + if (getAnvilCostMethod == null) return EnchantmentRarity.COMMON; try { int itemCost = (int) getAnvilCostMethod.invoke(enchantment); return EnchantmentRarity.getRarity(itemCost); } catch (IllegalAccessException | InvocationTargetException e) { - CustomAnvil.instance.getLogger().log(Level.SEVERE, "could not find cost for enchantment "+enchantment.getKey(), e); + CustomAnvil.instance.getLogger().log(Level.SEVERE, "could not find cost for enchantment " + enchantment.getKey(), e); return EnchantmentRarity.COMMON; } @@ -159,11 +164,11 @@ public class CABukkitEnchantment extends CAEnchantmentBase { @Override public boolean equals(Object obj) { - if(!(obj instanceof CABukkitEnchantment other)){ + if (!(obj instanceof CABukkitEnchantment other)) { return false; } - return this.enchantment.equals(other.getEnchant()); + return Objects.equals(this.bukkit, other.getEnchant()); } } diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEEnchantment.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEPreV5Enchantment.java similarity index 50% rename from src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEEnchantment.java rename to src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEPreV5Enchantment.java index 72c333f..783798d 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEEnchantment.java +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEPreV5Enchantment.java @@ -1,31 +1,46 @@ package xyz.alexcrea.cuanvil.enchant.wrapped; import org.bukkit.Material; +import org.bukkit.NamespacedKey; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; import su.nightexpress.excellentenchants.api.enchantment.CustomEnchantment; import su.nightexpress.excellentenchants.api.enchantment.Definition; import xyz.alexcrea.cuanvil.enchant.AdditionalTestEnchantment; import xyz.alexcrea.cuanvil.enchant.CAEnchantment; -import xyz.alexcrea.cuanvil.enchant.EnchantmentRarity; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.Map; import java.util.Set; -public class CAEEEnchantment extends CABukkitEnchantment implements AdditionalTestEnchantment { +public class CAEEPreV5Enchantment extends CABukkitEnchantment implements AdditionalTestEnchantment { @NotNull CustomEnchantment eeenchantment; @NotNull Definition definition; - public CAEEEnchantment(@NotNull CustomEnchantment enchantment) { - super(enchantment.getBukkitEnchantment(), EnchantmentRarity.getRarity(enchantment.getDefinition().getAnvilCost())); + public CAEEPreV5Enchantment(@NotNull CustomEnchantment enchantment) { + super(enchantment.getBukkitEnchantment(), getRarity(enchantment.getBukkitEnchantment())); this.eeenchantment = enchantment; - this.definition = enchantment.getDefinition(); + try { + this.definition = (Definition) getDefinition.invoke(enchantment); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } } + private final static Method getDefinition; + static { + try { + getDefinition = CustomEnchantment.class.getMethod("getDefinition"); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + @Override - public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull Material itemMat) { + public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemType) { if (!definition.hasConflicts()) return false; Set conflicts = definition.getConflicts(); @@ -38,8 +53,8 @@ public class CAEEEnchantment extends CABukkitEnchantment implements AdditionalTe } @Override - public boolean isItemConflict(@NotNull Map enchantments, @NotNull Material itemMat, @NotNull ItemStack item) { - if (Material.ENCHANTED_BOOK.equals(itemMat)) return false; + public boolean isItemConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemType, @NotNull ItemStack item) { + if (Material.ENCHANTED_BOOK.getKey().equals(itemType)) return false; return !definition.getSupportedItems().is(item); } diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEV5Enchantment.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEV5Enchantment.java new file mode 100644 index 0000000..2d8f945 --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEV5Enchantment.java @@ -0,0 +1,128 @@ +package xyz.alexcrea.cuanvil.enchant.wrapped; + +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nightexpress.excellentenchants.api.enchantment.CustomEnchantment; +import su.nightexpress.excellentenchants.api.item.ItemSet; +import xyz.alexcrea.cuanvil.enchant.AdditionalTestEnchantment; +import xyz.alexcrea.cuanvil.enchant.CAEnchantment; +import xyz.alexcrea.cuanvil.enchant.EnchantmentRarity; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.Set; + +public class CAEEV5Enchantment extends CABukkitEnchantment implements AdditionalTestEnchantment { + + @NotNull CustomEnchantment eeenchantment; + @NotNull Object definition; + + public CAEEV5Enchantment(@NotNull CustomEnchantment enchantment) { + super(enchantment.getBukkitEnchantment(), EnchantmentRarity.getRarity(getAnvilCost(enchantment))); + this.eeenchantment = enchantment; + this.definition = getDefinition(enchantment); + + } + + @Override + public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemType) { + if (!hasConflicts()) return false; + + Set conflicts = getExclusiveSet(); + + for (CAEnchantment caEnchantment : enchantments.keySet()) { + if (conflicts.contains(caEnchantment.getName())) return true; + if (conflicts.contains(caEnchantment.getKey().toString())) return true; + } + + return false; + } + + @Override + public boolean isItemConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemType, @NotNull ItemStack item) { + if (Material.ENCHANTED_BOOK.getKey().equals(itemType)) return false; + + String key = itemType.getKey(); + ItemSet primary = eeenchantment.getPrimaryItems(); + if (primary.getMaterials().contains(key)) return false; + + ItemSet supported = eeenchantment.getSupportedItems(); + if (supported.getMaterials().contains(key)) return false; + + return true; + } + + + private static final Method getDefinitonMethod; + + private static final Method getAnvilCostMethod; + private static final Method hasConflictsMethod; + private static final Method getExclusiveSetMethod; + static { + var enchClazz = CustomEnchantment.class; + try { + getDefinitonMethod = enchClazz.getDeclaredMethod("getDefinition"); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + + Class definitionClazz; + try { + definitionClazz = Class.forName("su.nightexpress.excellentenchants.api.EnchantDefinition"); + } catch (ClassNotFoundException e) { + try { + definitionClazz = Class.forName("su.nightexpress.excellentenchants.api.wrapper.EnchantDefinition"); + } catch (ClassNotFoundException ex) { + throw new RuntimeException(ex); + } + } + + // Now definition methods + try { + getAnvilCostMethod = definitionClazz.getDeclaredMethod("getAnvilCost"); + hasConflictsMethod = definitionClazz.getDeclaredMethod("hasConflicts"); + getExclusiveSetMethod = definitionClazz.getDeclaredMethod("getExclusiveSet"); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + + } + + private static Object getDefinition(CustomEnchantment enchantment) { + try { + return getDefinitonMethod.invoke(enchantment); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static int getAnvilCost(CustomEnchantment enchantment) { + try { + return (int) getAnvilCostMethod.invoke(getDefinition(enchantment)); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + private boolean hasConflicts() { + try { + return (boolean) hasConflictsMethod.invoke(definition); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + + private Set getExclusiveSet() { + try { + return (Set) getExclusiveSetMethod.invoke(definition); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEV5_4Enchantment.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEV5_4Enchantment.java new file mode 100644 index 0000000..7fb8627 --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEV5_4Enchantment.java @@ -0,0 +1,29 @@ +package xyz.alexcrea.cuanvil.enchant.wrapped; + +import org.bukkit.NamespacedKey; +import org.jetbrains.annotations.NotNull; +import su.nightexpress.excellentenchants.api.enchantment.CustomEnchantment; +import xyz.alexcrea.cuanvil.dependency.plugins.ExcellentEnchant5_4EnchantSettings; +import xyz.alexcrea.cuanvil.enchant.CAEnchantment; + +import java.util.Map; + +public class CAEEV5_4Enchantment extends CAEEV5Enchantment { + + public CAEEV5_4Enchantment(@NotNull CustomEnchantment enchantment) { + super(enchantment); + } + + @Override + public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemMat) { + if(super.isEnchantConflict(enchantments, itemMat)) return true; + + var limit = ExcellentEnchant5_4EnchantSettings.anvilLimit(); + var count = enchantments.keySet().stream() + .filter(key -> key instanceof CAEEV5_4Enchantment) + .count(); + + return count > limit; + } + +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEcoEnchant.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEcoEnchant.java index 6e74b73..32d1346 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEcoEnchant.java +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEcoEnchant.java @@ -4,6 +4,7 @@ import com.willfp.ecoenchants.enchant.EcoEnchant; import com.willfp.ecoenchants.target.EnchantmentTarget; import com.willfp.ecoenchants.type.EnchantmentType; import org.bukkit.Material; +import org.bukkit.NamespacedKey; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; import xyz.alexcrea.cuanvil.enchant.AdditionalTestEnchantment; @@ -23,9 +24,13 @@ public class CAEcoEnchant extends CABukkitEnchantment implements AdditionalTestE } @Override - public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull Material itemMat) { + public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemType) { if (enchantments.isEmpty()) return false; + // Check if there is only self + if (enchantments.size() == 1 && this.equals(enchantments.keySet().stream().findFirst().get())) + return false; + if (this.ecoEnchant.getConflictsWithEverything()) { return true; } @@ -57,9 +62,9 @@ public class CAEcoEnchant extends CABukkitEnchantment implements AdditionalTestE @Override public boolean isItemConflict(@NotNull Map enchantments, - @NotNull Material itemMat, + @NotNull NamespacedKey itemType, @NotNull ItemStack item) { - if (Material.ENCHANTED_BOOK.equals(itemMat)) { + if (Material.ENCHANTED_BOOK.getKey().equals(itemType)) { return false; } diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAIncompatibleAllEnchant.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAIncompatibleAllEnchant.java new file mode 100644 index 0000000..552ecd4 --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAIncompatibleAllEnchant.java @@ -0,0 +1,36 @@ +package xyz.alexcrea.cuanvil.enchant.wrapped; + +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import xyz.alexcrea.cuanvil.enchant.*; + +import java.util.Map; + +/** + * Represent an enchantment incompatible with every other enchantments + */ +public class CAIncompatibleAllEnchant extends CABukkitEnchantment implements AdditionalTestEnchantment { + + public CAIncompatibleAllEnchant(@NotNull Enchantment enchantment, @Nullable EnchantmentRarity rarity) { + super(enchantment, rarity); + } + + public CAIncompatibleAllEnchant(@NotNull Enchantment enchantment) { + super(enchantment); + } + + + @Override + public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemType) { + return !enchantments.isEmpty() && !(enchantments.size() == 1 && enchantments.containsKey(this)); + } + + @Override + public boolean isItemConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemType, @NotNull ItemStack item) { + return false; + } +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CALegacyEEEnchantment.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CALegacyEEEnchantment.java index 191f8f3..74068d4 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CALegacyEEEnchantment.java +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CALegacyEEEnchantment.java @@ -1,6 +1,7 @@ package xyz.alexcrea.cuanvil.enchant.wrapped; import org.bukkit.Material; +import org.bukkit.NamespacedKey; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; import su.nightexpress.excellentenchants.api.enchantment.EnchantmentData; @@ -22,7 +23,7 @@ public class CALegacyEEEnchantment extends CABukkitEnchantment implements Additi } @Override - public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull Material itemMat) { + public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemType) { if (!eeenchantment.hasConflicts()) return false; Set conflicts = eeenchantment.getConflicts(); @@ -35,8 +36,8 @@ public class CALegacyEEEnchantment extends CABukkitEnchantment implements Additi } @Override - public boolean isItemConflict(@NotNull Map enchantments, @NotNull Material itemMat, @NotNull ItemStack item) { - if (Material.ENCHANTED_BOOK.equals(itemMat)) return false; + public boolean isItemConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemType, @NotNull ItemStack item) { + if (Material.ENCHANTED_BOOK.getKey().equals(itemType)) return false; return !eeenchantment.getSupportedItems().is(item); } diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CALegacyEcoEnchant.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CALegacyEcoEnchant.java index 3b4242d..cb24def 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CALegacyEcoEnchant.java +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CALegacyEcoEnchant.java @@ -4,12 +4,14 @@ import com.willfp.ecoenchants.enchantments.EcoEnchant; import com.willfp.ecoenchants.enchantments.meta.EnchantmentTarget; import com.willfp.ecoenchants.enchantments.meta.EnchantmentType; import org.bukkit.Material; +import org.bukkit.NamespacedKey; import org.bukkit.enchantments.Enchantment; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; import xyz.alexcrea.cuanvil.enchant.AdditionalTestEnchantment; import xyz.alexcrea.cuanvil.enchant.CAEnchantment; import xyz.alexcrea.cuanvil.enchant.EnchantmentRarity; +import xyz.alexcrea.cuanvil.util.MaterialUtil; import java.util.Map; @@ -23,7 +25,7 @@ public class CALegacyEcoEnchant extends CABukkitEnchantment implements Additiona } @Override - public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull Material itemMat) { + public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemType) { if (enchantments.isEmpty()) return false; EnchantmentType type = this.ecoEnchant.getType(); @@ -48,14 +50,15 @@ public class CALegacyEcoEnchant extends CABukkitEnchantment implements Additiona @Override public boolean isItemConflict(@NotNull Map enchantments, - @NotNull Material itemMat, + @NotNull NamespacedKey itemType, @NotNull ItemStack item) { - if (Material.ENCHANTED_BOOK.equals(itemMat)) { + if (Material.ENCHANTED_BOOK.getKey().equals(itemType)) { return false; } + var mat = MaterialUtil.INSTANCE.getMatFromKey(itemType); for (EnchantmentTarget target : this.ecoEnchant.getTargets()) { - if (target.getMaterials().contains(itemMat)) { + if (target.getMaterials().contains(mat)) { return false; } } diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CASuperEnchantEnchantment.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CASuperEnchantEnchantment.java new file mode 100644 index 0000000..6039dc8 --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CASuperEnchantEnchantment.java @@ -0,0 +1,76 @@ +package xyz.alexcrea.cuanvil.enchant.wrapped; + +import com.maddoxh.superEnchants.enchants.CustomEnchant; +import com.maddoxh.superEnchants.enchants.EnchantManager; +import com.maddoxh.superEnchants.items.EnchantApplicator; +import com.maddoxh.superEnchants.items.EnchantReader; +import com.maddoxh.superEnchants.util.ConflictChecker; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; +import xyz.alexcrea.cuanvil.enchant.AdditionalTestEnchantment; +import xyz.alexcrea.cuanvil.enchant.CAEnchantment; +import xyz.alexcrea.cuanvil.enchant.CAEnchantmentBase; +import xyz.alexcrea.cuanvil.enchant.EnchantmentRarity; + +import java.util.HashMap; +import java.util.Map; + +public class CASuperEnchantEnchantment extends CAEnchantmentBase implements AdditionalTestEnchantment { + + private @NotNull CustomEnchant enchant; + private @NotNull EnchantManager enchantManager; + + public CASuperEnchantEnchantment(@NotNull CustomEnchant enchant, @NotNull Plugin plugin, @NotNull EnchantManager enchantManager) { + super(NamespacedKey.fromString(enchant.getId(), plugin), EnchantmentRarity.COMMON, enchant.getMaxLevel()); + + this.enchant = enchant; + this.enchantManager = enchantManager; + } + + @Override + public int getLevel(@NotNull ItemStack item, @NotNull ItemMeta meta) { + return EnchantReader.INSTANCE.getEnchantLevel(item, enchant.getId()); + } + + @Override + public boolean isEnchantmentPresent(@NotNull ItemStack item, @NotNull ItemMeta meta) { + return EnchantReader.INSTANCE.hasEnchant(item, enchant.getId()); + } + + @Override + public void addEnchantmentUnsafe(@NotNull ItemStack item, int level) { + EnchantApplicator.INSTANCE.applyEnchant(item, enchant.getId(), level); + } + + @Override + public void removeFrom(@NotNull ItemStack item) { + EnchantApplicator.INSTANCE.removeEnchant(item, enchant.getId()); + } + + @Override + public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemType) { + var idMap = new HashMap(); + + enchantments.forEach((enchant, level) -> { + if(!(enchant instanceof CASuperEnchantEnchantment superEnch)) return; + idMap.put(superEnch.enchant.getId(), level); + }); + + return ConflictChecker.INSTANCE.hasConflict( + idMap, + enchant.getId(), + enchantManager + ) != null; + } + + @Override + public boolean isItemConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemType, @NotNull ItemStack item) { + if(Material.ENCHANTED_BOOK.equals(item.getType())) return false; + + return !enchant.canApplyTo(item.getType()); + } +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/MainConfigGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/MainConfigGui.java index e2224f7..cc4fddc 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/MainConfigGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/MainConfigGui.java @@ -103,8 +103,8 @@ public class MainConfigGui extends ChestGui { ItemMeta groupMeta = groupItemstack.getItemMeta(); assert groupMeta != null; - groupMeta.setDisplayName("§aGroups"); - groupMeta.setLore(Collections.singletonList("§7Click here to open material group menu")); + groupMeta.setDisplayName("§aItem Groups"); + groupMeta.setLore(Collections.singletonList("§7Click here to open item group menu")); groupItemstack.setItemMeta(groupMeta); GuiItem groupConfigItem = GuiGlobalItems.goToGuiItem(groupItemstack, GroupConfigGui.getInstance()); diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/SelectMaterialContainer.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/SelectMaterialContainer.java index 2f76694..3756341 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/SelectMaterialContainer.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/SelectMaterialContainer.java @@ -1,34 +1,35 @@ package xyz.alexcrea.cuanvil.gui.config; import org.bukkit.Material; +import org.bukkit.NamespacedKey; import xyz.alexcrea.cuanvil.util.CasedStringUtil; import java.util.*; public interface SelectMaterialContainer { - EnumSet getSelectedMaterials(); + Set getSelectedMaterials(); - boolean setSelectedMaterials(EnumSet materials); + boolean setSelectedMaterials(Set materials); - EnumSet illegalMaterials(); + Set illegalMaterials(); static List getMaterialLore(SelectMaterialContainer container, String containerType, String action){ // Prepare material lore ArrayList groupLore = new ArrayList<>(); groupLore.add("§7Allow you to select a list of §ematerials §7that this " + containerType + " should " + action); - Set materialSet = container.getSelectedMaterials(); + Set materialSet = container.getSelectedMaterials(); if (materialSet.isEmpty()) { groupLore.add("§7There is no "+action+"d material for this "+containerType+"."); } else { groupLore.add("§7List of "+action+"d materials for this "+containerType+":"); - Iterator materialIterator = materialSet.iterator(); + Iterator materialIterator = materialSet.iterator(); boolean greaterThanMax = materialSet.size() > 5; int maxindex = (greaterThanMax ? 4 : materialSet.size()); for (int i = 0; i < maxindex; i++) { // format string like "- Stone Sword" - String formattedName = CasedStringUtil.snakeToUpperSpacedCase(materialIterator.next().name().toLowerCase()); + String formattedName = CasedStringUtil.snakeToUpperSpacedCase(materialIterator.next().getKey().toLowerCase()); groupLore.add("§7- §e" + formattedName); } diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/ask/ConfirmActionGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/ask/ConfirmActionGui.java index 56bf848..5839663 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/ask/ConfirmActionGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/ask/ConfirmActionGui.java @@ -11,6 +11,7 @@ import org.bukkit.inventory.meta.ItemMeta; import org.jetbrains.annotations.NotNull; import xyz.alexcrea.cuanvil.gui.util.GuiGlobalActions; import xyz.alexcrea.cuanvil.gui.util.GuiSharedConstant; +import xyz.alexcrea.cuanvil.util.MetricsUtil; import java.util.Arrays; import java.util.function.Supplier; @@ -41,6 +42,7 @@ public class ConfirmActionGui extends AbstractAskGui { success = onConfirm.get(); } catch (Exception e) { CustomAnvil.instance.getLogger().log(Level.WARNING, "Could not process confirmation supplier.", e); + MetricsUtil.INSTANCE.trackError(e); success = false; } diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/ask/SelectItemTypeGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/ask/SelectItemTypeGui.java index b2d6afe..66411bd 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/ask/SelectItemTypeGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/ask/SelectItemTypeGui.java @@ -12,6 +12,7 @@ import org.jetbrains.annotations.NotNull; import xyz.alexcrea.cuanvil.gui.util.GuiGlobalActions; import xyz.alexcrea.cuanvil.gui.util.GuiGlobalItems; import xyz.alexcrea.cuanvil.gui.util.GuiSharedConstant; +import xyz.alexcrea.cuanvil.util.MaterialUtil; import java.util.Arrays; import java.util.concurrent.atomic.AtomicReference; @@ -52,7 +53,7 @@ public class SelectItemTypeGui extends AbstractAskGui { event.setCancelled(true); ItemStack cursor = event.getWhoClicked().getItemOnCursor(); - if(cursor.getType().isAir()) return; + if(MaterialUtil.INSTANCE.isAir(cursor)) return; ItemStack finalItem; if(materialOnly){ diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/AbstractEnchantConfigGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/AbstractEnchantConfigGui.java index a65b54b..6bd7ea3 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/AbstractEnchantConfigGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/AbstractEnchantConfigGui.java @@ -20,7 +20,7 @@ import java.util.function.Consumer; * * @param Type of the factory of the type of setting the gui should edit. */ -public abstract class AbstractEnchantConfigGui extends SettingGuiListConfigGui implements ValueUpdatableGui { +public abstract class AbstractEnchantConfigGui extends SettingGuiListConfigGui{ /** * Constructor for a gui displaying available enchantment to edit a enchantment setting. diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/BasicConfigGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/BasicConfigGui.java index a07cb3c..51936c7 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/BasicConfigGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/BasicConfigGui.java @@ -14,6 +14,7 @@ import org.bukkit.inventory.meta.ItemMeta; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import xyz.alexcrea.cuanvil.config.ConfigHolder; +import xyz.alexcrea.cuanvil.dependency.MinecraftVersionUtil; import xyz.alexcrea.cuanvil.dependency.packet.PacketManager; import xyz.alexcrea.cuanvil.gui.ValueUpdatableGui; import xyz.alexcrea.cuanvil.gui.config.MainConfigGui; @@ -283,7 +284,7 @@ public class BasicConfigGui extends ChestGui implements ValueUpdatableGui { if(!this.packetManager.getCanSetInstantBuild()){ lore.add(""); - lore.add("§4/!\\§cCaution§4/!\\ §cYou need ProtocoLib installed and working or a newer version of this plugin for this to work."); + lore.add("§4/!\\§cCaution§4/!\\ §cYou need ProtocoLib installed and working or a paper server."); lore.add("§cCurrently ProtocoLib is not detected."); } diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/CustomRecipeConfigGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/CustomRecipeConfigGui.java index feaa8dd..e21ad75 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/CustomRecipeConfigGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/CustomRecipeConfigGui.java @@ -14,7 +14,7 @@ import xyz.alexcrea.cuanvil.gui.util.GuiSharedConstant; import xyz.alexcrea.cuanvil.recipe.AnvilCustomRecipe; import xyz.alexcrea.cuanvil.util.CasedStringUtil; -import java.util.Arrays; +import java.util.ArrayList; import java.util.Collection; public class CustomRecipeConfigGui extends MappedGuiListConfigGui getRecipeLore(AnvilCustomRecipe recipe) { + boolean shouldWork = recipe.validate(); + + ArrayList lore = new ArrayList<>(); + lore.add("§7Is valid: §" + (shouldWork ? "aYes" : "cNo")); + lore.add("§7Exact count: §" + (recipe.getExactCount() ? "aYes" : "cNo")); + lore.add("§7Recipe Level Cost: §e" + recipe.getLevelCostPerCraft()); + lore.add("§7Recipe Linear Xp Cost: §e" + recipe.getXpCostPerCraft()); + if (recipe.getXpCostPerCraft() != 0) { + lore.add("§7Exact Linear xp remove: §" + (recipe.getRemoveExactLinearXp() ? "aYes" : "cNo")); + } + return lore; + } + @Override protected LazyElement newInstanceOfGui(AnvilCustomRecipe generic, GuiItem item) { return new LazyElement<>(item, () -> new CustomRecipeSubSettingGui(this, generic)); @@ -87,7 +94,11 @@ public class CustomRecipeConfigGui extends MappedGuiListConfigGui { - private static final String SECTION_NAME = "enchant_limits"; + private static final String SECTION_NAME = ConfigOptions.ENCHANT_LIMIT_ROOT; private static EnchantLimitConfigGui INSTANCE = null; @@ -41,18 +41,34 @@ public class EnchantLimitConfigGui extends AbstractEnchantConfigGui "Default (" + defaultValue + ")"; + case RESET -> String.valueOf(defaultValue); + default -> "Default"; + }; + + } + else return super.valueDisplayName(type, value); } }; } diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/CustomRecipeSubSettingGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/CustomRecipeSubSettingGui.java index d5ca2c8..9db6d62 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/CustomRecipeSubSettingGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/CustomRecipeSubSettingGui.java @@ -36,26 +36,32 @@ public class CustomRecipeSubSettingGui extends MappedToListSubSettingGui { public CustomRecipeSubSettingGui( @NotNull CustomRecipeConfigGui parent, @NotNull AnvilCustomRecipe anvilRecipe) { - super(3, "§e" + CasedStringUtil.snakeToUpperSpacedCase(anvilRecipe.toString()) + " §8Config"); + super(4, "§e" + CasedStringUtil.snakeToUpperSpacedCase(anvilRecipe.toString()) + " §8Config"); this.parent = parent; this.anvilRecipe = anvilRecipe; Pattern pattern = new Pattern( GuiSharedConstant.EMPTY_GUI_FULL_LINE, "01203450D", + "0ab000000", "B00000000" ); - this.pane = new PatternPane(0, 0, 9, 3, pattern); + this.pane = new PatternPane(0, 0, 9, 4, pattern); addPane(this.pane); prepareStaticValues(); } - BoolSettingsGui.BoolSettingFactory exactCountFactory; - IntSettingsGui.IntSettingFactory xpCostFactory; - ItemSettingGui.ItemSettingFactory leftItemFactory; - ItemSettingGui.ItemSettingFactory rightItemFactory; - ItemSettingGui.ItemSettingFactory resultItemFactory; + private BoolSettingsGui.BoolSettingFactory exactCountFactory; + private BoolSettingsGui.BoolSettingFactory removeExactLinearXpFactory; + private GuiItem noRemoveExactLinearXp; + + private IntSettingsGui.IntSettingFactory levelCostFactory; + private IntSettingsGui.IntSettingFactory linearXpCostFactory; + + private ItemSettingGui.ItemSettingFactory leftItemFactory; + private ItemSettingGui.ItemSettingFactory rightItemFactory; + private ItemSettingGui.ItemSettingFactory resultItemFactory; private void prepareStaticValues() { @@ -74,19 +80,38 @@ public class CustomRecipeSubSettingGui extends MappedToListSubSettingGui { this.pane.bindItem('D', new GuiItem(deleteItem, GuiGlobalActions.openGuiAction(createDeleteGui()), CustomAnvil.instance)); // Displayed item will be updated later - IntRange costRange = AnvilCustomRecipe.Companion.getXP_COST_CONFIG_RANGE(); this.exactCountFactory = new BoolSettingsGui.BoolSettingFactory("§8Exact count ?", this, ConfigHolder.CUSTOM_RECIPE_HOLDER, this.anvilRecipe + "." + AnvilCustomRecipe.EXACT_COUNT_CONFIG, AnvilCustomRecipe.DEFAULT_EXACT_COUNT_CONFIG); - this.xpCostFactory = new IntSettingsGui.IntSettingFactory("§8Recipe Xp Cost", this, - this.anvilRecipe +"."+AnvilCustomRecipe.XP_COST_CONFIG, + this.removeExactLinearXpFactory = new BoolSettingsGui.BoolSettingFactory("§8Remove exact linear xp ?", this, + ConfigHolder.CUSTOM_RECIPE_HOLDER, + this.anvilRecipe + "." + AnvilCustomRecipe.REMOVE_EXACT_XP_CONFIG, AnvilCustomRecipe.DEFAULT_REMOVE_EXACT_XP_CONFIG); + + ItemStack item = new ItemStack(Material.BARRIER); + ItemMeta meta = item.getItemMeta(); + assert meta != null; + + meta.setDisplayName("§cRemove exact linear xp ?"); + meta.setLore(Collections.singletonList("§7Not usable if linear cost is 0")); + item.setItemMeta(meta); + this.noRemoveExactLinearXp = new GuiItem(item, GuiGlobalActions.stayInPlace, CustomAnvil.instance); + + this.levelCostFactory = new IntSettingsGui.IntSettingFactory("§8Recipe Level Cost", this, + this.anvilRecipe + "." + AnvilCustomRecipe.XP_LEVEL_COST_CONFIG, ConfigHolder.CUSTOM_RECIPE_HOLDER, null, - costRange.getFirst(), costRange.getLast(), AnvilCustomRecipe.DEFAULT_XP_COST_CONFIG, 1, 5, 10); + costRange.getFirst(), costRange.getLast(), AnvilCustomRecipe.DEFAULT_XP_LEVEL_COST_CONFIG, 1, 5, 10); + + this.linearXpCostFactory = new IntSettingsGui.IntSettingFactory("§8Recipe Linear Xp Cost", this, + this.anvilRecipe + "." + AnvilCustomRecipe.LINEAR_XP_COST_CONFIG, + ConfigHolder.CUSTOM_RECIPE_HOLDER, + null, + 0, Integer.MAX_VALUE, AnvilCustomRecipe.DEFAULT_LINEAR_XP_COST_CONFIG, 1, 10, 100, 1000, 10000); + // Right part of the gui this.leftItemFactory = new ItemSettingGui.ItemSettingFactory("§eRecipe Left §8Item", this, this.anvilRecipe + "." + AnvilCustomRecipe.LEFT_ITEM_CONFIG, ConfigHolder.CUSTOM_RECIPE_HOLDER, @@ -158,8 +183,18 @@ public class CustomRecipeSubSettingGui extends MappedToListSubSettingGui { GuiItem exactCountItem = this.exactCountFactory.getItem(); this.pane.bindItem('1', exactCountItem); - GuiItem xpCostItem = this.xpCostFactory.getItem(Material.EXPERIENCE_BOTTLE); - this.pane.bindItem('2', xpCostItem); + if (anvilRecipe.getXpCostPerCraft() == 0) { + this.pane.bindItem('a', noRemoveExactLinearXp); + } else { + this.pane.bindItem('a', removeExactLinearXpFactory.getItem()); + } + + GuiItem levelCostItem = this.levelCostFactory.getItem(Material.EXPERIENCE_BOTTLE); + this.pane.bindItem('2', levelCostItem); + + + GuiItem xpCostItem = this.linearXpCostFactory.getItem(Material.EXPERIENCE_BOTTLE); + this.pane.bindItem('b', xpCostItem); GuiItem leftGuiItem = this.leftItemFactory.getItem(); this.pane.bindItem('3', leftGuiItem); @@ -169,7 +204,7 @@ public class CustomRecipeSubSettingGui extends MappedToListSubSettingGui { GuiItem resultGuiItem = this.resultItemFactory.getItem(); this.pane.bindItem('5', resultGuiItem); - + update(); } diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/EnchantConflictSubSettingGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/EnchantConflictSubSettingGui.java index 99bba84..97bdfcb 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/EnchantConflictSubSettingGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/EnchantConflictSubSettingGui.java @@ -25,6 +25,7 @@ import xyz.alexcrea.cuanvil.gui.util.GuiGlobalActions; import xyz.alexcrea.cuanvil.gui.util.GuiGlobalItems; import xyz.alexcrea.cuanvil.gui.util.GuiSharedConstant; import xyz.alexcrea.cuanvil.util.CasedStringUtil; +import xyz.alexcrea.cuanvil.util.MetricsUtil; import java.util.*; import java.util.function.Supplier; @@ -264,6 +265,7 @@ public class EnchantConflictSubSettingGui extends MappedToListSubSettingGui impl updateGuiValues(); } catch (Exception e) { CustomAnvil.instance.getLogger().log(Level.WARNING, "An error occurred while updating enchants for " + this.enchantConflict, e); + MetricsUtil.INSTANCE.trackError(e); } // Save file configuration to disk @@ -308,6 +310,7 @@ public class EnchantConflictSubSettingGui extends MappedToListSubSettingGui impl updateGuiValues(); } catch (Exception e) { CustomAnvil.instance.getLogger().log(Level.WARNING, "An error occurred while updating group for " + this.enchantConflict, e); + MetricsUtil.INSTANCE.trackError(e); } // Save file configuration to disk diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/GroupConfigSubSettingGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/GroupConfigSubSettingGui.java index efa1117..3d05674 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/GroupConfigSubSettingGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/GroupConfigSubSettingGui.java @@ -5,6 +5,7 @@ import com.github.stefvanschie.inventoryframework.pane.PatternPane; import com.github.stefvanschie.inventoryframework.pane.util.Pattern; import io.delilaheve.CustomAnvil; import org.bukkit.Material; +import org.bukkit.NamespacedKey; import org.bukkit.entity.HumanEntity; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.inventory.ItemFlag; @@ -39,7 +40,7 @@ public class GroupConfigSubSettingGui extends MappedToListSubSettingGui implemen @NotNull GroupConfigGui parent, @NotNull IncludeGroup group) { super(3, - CasedStringUtil.snakeToUpperSpacedCase(group.getName()) + " Config"); + "§e" + CasedStringUtil.snakeToUpperSpacedCase(group.getName()) + " §rConfig"); this.parent = parent; this.group = group; @@ -71,20 +72,31 @@ public class GroupConfigSubSettingGui extends MappedToListSubSettingGui implemen this.pane.bindItem('D', new GuiItem(deleteItem, openGuiAndCheckAction(), CustomAnvil.instance)); // Displayed item will be updated later - this.materialSelection = new GuiItem(new ItemStack(Material.DIAMOND_SWORD), (event) -> { - event.setCancelled(true); + String materialSelectionName = "§e" + CasedStringUtil.snakeToUpperSpacedCase(group.getName()) + " §rMaterials"; + ItemStack selectItem = new ItemStack(Material.DIAMOND_SWORD); + ItemMeta selectItemMeta = selectItem.getItemMeta(); + selectItemMeta.setDisplayName(materialSelectionName); + selectItem.setItemMeta(selectItemMeta); + this.materialSelection = new GuiItem(selectItem, (event) -> { + event.setCancelled(true); MaterialSelectSettingGui selectGui = new MaterialSelectSettingGui(this, - CasedStringUtil.snakeToUpperSpacedCase(group.getName()) + " Materials" + materialSelectionName , this); selectGui.show(event.getWhoClicked()); }, CustomAnvil.instance); - this.groupSelection = new GuiItem(new ItemStack(Material.CHEST), (event) -> { + String selectGroupName = "§e" + CasedStringUtil.snakeToUpperSpacedCase(this.group.getName()) + " §rGroups"; + ItemStack selectGroup = new ItemStack(Material.CHEST); + ItemMeta selectGroupMeta = selectGroup.getItemMeta(); + selectGroupMeta.setDisplayName(selectGroupName); + + selectGroup.setItemMeta(selectGroupMeta); + this.groupSelection = new GuiItem(selectGroup, (event) -> { event.setCancelled(true); GroupSelectSettingGui enchantGui = new GroupSelectSettingGui( - CasedStringUtil.snakeToUpperSpacedCase(this.group.getName()) + " Groups", + selectGroupName, this, this, 0); enchantGui.show(event.getWhoClicked()); }, CustomAnvil.instance); @@ -310,23 +322,23 @@ public class GroupConfigSubSettingGui extends MappedToListSubSettingGui implemen // ---------------------------- // End of SelectGroupContainer related methods // ---------------------------- - // SelectGroupContainer related methods + // SelectMaterialContainer related methods // ---------------------------- @Override - public EnumSet getSelectedMaterials() { + public Set getSelectedMaterials() { return this.group.getNonGroupInheritedMaterials(); } @Override - public boolean setSelectedMaterials(EnumSet materials) { + public boolean setSelectedMaterials(Set materials) { this.group.setNonGroupInheritedMaterials(materials); // Write to file configuration String[] groupNames = new String[materials.size()]; int index = 0; - for (Material otherGroup : materials) { - groupNames[index++] = otherGroup.name().toLowerCase(); + for (NamespacedKey otherGroup : materials) { + groupNames[index++] = otherGroup.getKey().toLowerCase(); } ConfigHolder.ITEM_GROUP_HOLDER.getConfig().set(this.group.getName()+"."+ItemGroupManager.MATERIAL_LIST_PATH, groupNames); @@ -342,12 +354,12 @@ public class GroupConfigSubSettingGui extends MappedToListSubSettingGui implemen } @Override - public EnumSet illegalMaterials() { - return EnumSet.of(Material.AIR); + public Set illegalMaterials() { + return Set.of(Material.AIR.getKey()); } // ---------------------------- - // End of SelectGroupContainer related methods + // End of SelectMaterialContainer related methods // ---------------------------- private void updateDirectReferencingGroups(AbstractMaterialGroup referenceTo){ @@ -370,7 +382,7 @@ public class GroupConfigSubSettingGui extends MappedToListSubSettingGui implemen for (AbstractMaterialGroup otherGroup : everyStoredGroups) { if(otherGroup.getGroups().contains(testGroup)){ otherGroup.updateMaterials(); - toUpdate.add(otherGroup); + updateFuture.add(otherGroup); } } diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/IntSettingsGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/IntSettingsGui.java index af977e9..73121a6 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/IntSettingsGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/IntSettingsGui.java @@ -72,7 +72,8 @@ public class IntSettingsGui extends AbstractSettingGui { assert meta != null; meta.setDisplayName("§eReset to default value"); - meta.setLore(Collections.singletonList("§7Default value is §e" + holder.defaultVal)); + meta.setLore(Collections.singletonList("§7Default value is §e" + + holder.valueDisplayName(ValueDisplayType.RESET, holder.defaultVal))); item.setItemMeta(meta); returnToDefault = new GuiItem(item, event -> { event.setCancelled(true); @@ -86,41 +87,23 @@ public class IntSettingsGui extends AbstractSettingGui { * Update item using the setting value to match the new value. */ protected void updateValueDisplay() { - PatternPane pane = getPane(); // minus item GuiItem minusItem; if (now > holder.min) { int planned = Math.max(holder.min, now - step); - ItemStack item = new ItemStack(Material.RED_TERRACOTTA); - ItemMeta meta = item.getItemMeta(); - assert meta != null; - - meta.setDisplayName("§e" + now + " §f-> §e" + planned + " §r(§c-" + (now - planned) + "§r)"); - meta.setLore(Collections.singletonList(AbstractSettingGui.CLICK_LORE)); - item.setItemMeta(meta); - - minusItem = new GuiItem(item, updateNowConsumer(planned), CustomAnvil.instance); + minusItem = valueEditItem(Material.RED_TERRACOTTA, ValueDisplayType.REMOVE, planned); } else { minusItem = GuiGlobalItems.backgroundItem(Material.BARRIER); } pane.bindItem('-', minusItem); //plus item - // may do a function to generalise ? GuiItem plusItem; if (now < holder.max) { int planned = Math.min(holder.max, now + step); - ItemStack item = new ItemStack(Material.GREEN_TERRACOTTA); - ItemMeta meta = item.getItemMeta(); - assert meta != null; - - meta.setDisplayName("§e" + now + " §f-> §e" + planned + " §r(§a+" + (planned - now) + "§r)"); - meta.setLore(Collections.singletonList(AbstractSettingGui.CLICK_LORE)); - item.setItemMeta(meta); - - plusItem = new GuiItem(item, updateNowConsumer(planned), CustomAnvil.instance); + plusItem = valueEditItem(Material.GREEN_TERRACOTTA, ValueDisplayType.ADD, planned); } else { plusItem = GuiGlobalItems.backgroundItem(Material.BARRIER); } @@ -131,7 +114,7 @@ public class IntSettingsGui extends AbstractSettingGui { ItemMeta resultMeta = resultPaper.getItemMeta(); assert resultMeta != null; - resultMeta.setDisplayName("§fValue: §e" + now); + resultMeta.setDisplayName("§fValue: §e" + holder.valueDisplayName(ValueDisplayType.CURRENT, now)); resultMeta.setLore(holder.displayLore); resultPaper.setItemMeta(resultMeta); @@ -149,7 +132,21 @@ public class IntSettingsGui extends AbstractSettingGui { } pane.bindItem('D', returnToDefault); + } + private GuiItem valueEditItem(Material mat, ValueDisplayType type, int planned) { + ItemStack item = new ItemStack(mat); + ItemMeta meta = item.getItemMeta(); + assert meta != null; + + var nowDisplay = holder.valueDisplayName(type, now); + var plannedDisplay = holder.valueDisplayName(type, planned); + var deltaDisplay = holder.deltaDisplay(type, now, planned); + meta.setDisplayName("§e" + nowDisplay + " §f-> §e" + plannedDisplay + " §r(§c" + deltaDisplay + "§r)"); + + meta.setLore(Collections.singletonList(AbstractSettingGui.CLICK_LORE)); + item.setItemMeta(meta); + return new GuiItem(item, updateNowConsumer(planned), CustomAnvil.instance); } /** @@ -389,6 +386,23 @@ public class IntSettingsGui extends AbstractSettingGui { return getItem(itemMat, CasedStringUtil.detectToUpperSpacedCase(configPath)); } + protected String valueDisplayName(ValueDisplayType type, int value) { + return String.valueOf(value); + } + + protected String deltaDisplay(ValueDisplayType type, int now, int planned) { + var delta = planned - now; + if(delta < 0) return "§c" + delta; + else return "§a+" + delta; + } + + } + + public enum ValueDisplayType { + ADD, + CURRENT, + REMOVE, + RESET, } } diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/MaterialSelectSettingGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/MaterialSelectSettingGui.java index a3963ce..fc519ff 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/MaterialSelectSettingGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/MaterialSelectSettingGui.java @@ -5,6 +5,7 @@ import com.github.stefvanschie.inventoryframework.gui.type.util.Gui; import com.github.stefvanschie.inventoryframework.pane.util.Pattern; import io.delilaheve.CustomAnvil; import org.bukkit.Material; +import org.bukkit.NamespacedKey; import org.bukkit.entity.HumanEntity; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.inventory.ItemFlag; @@ -18,18 +19,19 @@ import xyz.alexcrea.cuanvil.gui.util.GuiGlobalActions; import xyz.alexcrea.cuanvil.gui.util.GuiGlobalItems; import xyz.alexcrea.cuanvil.gui.util.GuiSharedConstant; import xyz.alexcrea.cuanvil.util.CasedStringUtil; +import xyz.alexcrea.cuanvil.util.MaterialUtil; import java.util.*; import java.util.function.Consumer; -public class MaterialSelectSettingGui extends MappedElementListConfigGui { +public class MaterialSelectSettingGui extends MappedElementListConfigGui { private final SelectMaterialContainer selector; private final Gui backGui; private boolean instantRemove; - private final List defaultMaterials; - private final EnumSet illegalMaterials; + private final List defaultMaterials; + private final Set illegalMaterials; private final int defaultMaterialHash; private int nowMaterialHash; @@ -161,8 +163,7 @@ public class MaterialSelectSettingGui extends MappedElementListConfigGui result = EnumSet.noneOf(Material.class); - result.addAll(this.elementGuiMap.keySet()); + Set result = new HashSet<>(this.elementGuiMap.keySet()); if(!this.selector.setSelectedMaterials(result)){ player.sendMessage("§cSomething went wrong while saving the change of value."); @@ -185,8 +186,8 @@ public class MaterialSelectSettingGui extends MappedElementListConfigGui getEveryDisplayableInstanceOfGeneric() { + protected Collection getEveryDisplayableInstanceOfGeneric() { return this.defaultMaterials; } @Override - protected void updateElement(Material material, GuiItem element) { + protected void updateElement(NamespacedKey material, GuiItem element) { // Nothing happen here I think } @Override - protected GuiItem newElementRequested(Material material, GuiItem newItem) { + protected GuiItem newElementRequested(NamespacedKey material, GuiItem newItem) { newItem.setAction(event -> { if(this.instantRemove){ removeMaterial(material); }else { - String materialName = CasedStringUtil.snakeToUpperSpacedCase(material.name().toLowerCase()); + String materialName = CasedStringUtil.snakeToUpperSpacedCase(material.getKey().toLowerCase()); // Create and show confirm remove gui. ConfirmActionGui confirmGui = new ConfirmActionGui( @@ -250,7 +251,7 @@ public class MaterialSelectSettingGui extends MappedElementListConfigGui materialList){ + private static int hashFromMaterialList(List materialList){ int defaultMaterialHash = 0; - for (Material material : materialList) { + for (NamespacedKey material : materialList) { defaultMaterialHash ^= material.hashCode(); } return defaultMaterialHash; diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/WorkPenaltyTypeSettingGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/WorkPenaltyTypeSettingGui.java index 888aa25..4345aa1 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/WorkPenaltyTypeSettingGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/WorkPenaltyTypeSettingGui.java @@ -11,11 +11,11 @@ import org.bukkit.entity.HumanEntity; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.jetbrains.annotations.NotNull; +import xyz.alexcrea.cuanvil.anvil.AnvilUseType; import xyz.alexcrea.cuanvil.config.ConfigHolder; import xyz.alexcrea.cuanvil.config.WorkPenaltyType; import xyz.alexcrea.cuanvil.gui.config.global.BasicConfigGui; import xyz.alexcrea.cuanvil.gui.util.GuiGlobalActions; -import xyz.alexcrea.cuanvil.util.AnvilUseType; import java.util.ArrayList; import java.util.EnumMap; diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/ModrinthUpdateChecker.java b/src/main/java/xyz/alexcrea/cuanvil/update/ModrinthUpdateChecker.java new file mode 100644 index 0000000..489c636 --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/update/ModrinthUpdateChecker.java @@ -0,0 +1,214 @@ +/* + * MIT License + * + * Copyright (c) 2025 Clickism + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +package xyz.alexcrea.cuanvil.update; + +import com.google.gson.*; +import org.jetbrains.annotations.Nullable; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Utility class to check for newer versions of a project hosted on Modrinth. + */ +public class ModrinthUpdateChecker { + + private static final String API_URL = "https://api.modrinth.com/v2/project/{id}/version"; + + private final String projectId; + private final String loader; + @Nullable + private final String minecraftVersion; + + @Nullable + private Boolean featured = null; + + @Nullable + public Consumer onError = null; + @Nullable + public Function getRawVersion = ModrinthUpdateChecker::getRawVersion; + + /** + * Create a new update checker for the given project. + * This will check the latest version for the given loader and any minecraft version. + * + * @param projectId the project ID + * @param loader the loader + */ + public ModrinthUpdateChecker(String projectId, String loader) { + this(projectId, loader, null); + } + + /** + * Create a new update checker for the given project. + * This will check the latest version for the given loader and minecraft version. + * + * @param projectId the project ID + * @param loader the loader + * @param minecraftVersion the minecraft version, or null for any version + */ + public ModrinthUpdateChecker(String projectId, String loader, @Nullable String minecraftVersion) { + this.projectId = projectId; + this.loader = loader; + this.minecraftVersion = minecraftVersion; + } + + /** + * Check the latest version of the project for the given loader and minecraft version + * and call the consumer with it. + * + * @param consumer the consumer + */ + public void checkVersion(Consumer consumer) { + try { + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request = HttpRequest.newBuilder() + .uri(prepareURI()) + .GET() + .build(); + + client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) + .thenAcceptAsync(response -> { + if (response.statusCode() != 200) { + if(onError != null) + onError.accept(new RuntimeException("wrong response status code: " + response.statusCode())); + return; + } + JsonArray versionsArray = JsonParser.parseString(response.body()).getAsJsonArray(); + String latestVersion = getLatestVersion(versionsArray); + if (latestVersion == null) { + if(onError != null) + onError.accept(new RuntimeException("latest version is null")); + return; + } + consumer.accept(latestVersion); + }); + } catch (Exception e) { + if(onError != null) onError.accept(e); + } + } + + /** + * Get the latest compatible version from the versions array. + * + * @param versions the versions array + * @return the latest compatible version + */ + @Nullable + protected String getLatestVersion(JsonArray versions) { + return versions.asList().stream().findFirst() + .map(JsonElement::getAsJsonObject) + .map(version -> version.get("version_number").getAsString()) + .map(getRawVersion != null ? getRawVersion : (v -> v)) + .orElse(null); + } + + /** + * Gets the raw version from a version string. + * i.E: "fabric-1.2+1.17.1" -> "1.2" + * + * @param version the version string + * @return the raw version string + */ + public static String getRawVersion(String version) { + if (version.isEmpty()) return version; + version = version.replaceAll("^\\D+", ""); + String[] split = version.split("\\+"); + return split[0]; + } + + /** + * Prepare this request uri based on current parameters. + * @return the request uri + */ + private URI prepareURI() { + var url = new StringBuilder(API_URL.replace("{id}", projectId)); + + var parameters = prepareParameters(); + String[] paramArray = new String[parameters.size()]; + int i = 0; + for (Map.Entry entry : parameters.entrySet()) { + paramArray[i++] = entry.getKey() + '=' + entry.getValue(); + } + url.append('?').append(String.join("&", paramArray)); + + return URI.create(url.toString()); + } + + /** + * Get the parameters for the version request. + * + * @return a map of key-value map of the request parameters + */ + private Map prepareParameters(){ + var parameters = new HashMap(); + + parameters.put("loaders", List.of(loader).toString()); + if(minecraftVersion != null) parameters.put("game_versions", List.of(minecraftVersion).toString()); + if(featured != null) parameters.put("featured", featured.toString()); + + parameters.put("include_changelog", "false"); + return parameters; + } + + /** + * Only get featured or non-featured versions. + * Null represent no filter. + * @param featured should be restricted to featured version ? default null if not called + * @return this + */ + public ModrinthUpdateChecker setFeatured(@Nullable Boolean featured) { + this.featured = featured; + return this; + } + + /** + * Function called on error calling the api. + * @param onError What should happen on error + * @return this + */ + public ModrinthUpdateChecker setOnError(@Nullable Consumer onError) { + this.onError = onError; + return this; + } + + /** + * Set the function to get raw version from the modrinth version. + * If null provided raw version will act as in the identity function. + * @param getRawVersion The function transforming modrinth version to raw version + * @return this + */ + public ModrinthUpdateChecker setGetRawVersion(@Nullable Function getRawVersion) { + this.getRawVersion = getRawVersion; + return this; + } +} \ No newline at end of file diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/PluginSetDefault.java b/src/main/java/xyz/alexcrea/cuanvil/update/PluginSetDefault.java index 363bd6a..707a218 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/update/PluginSetDefault.java +++ b/src/main/java/xyz/alexcrea/cuanvil/update/PluginSetDefault.java @@ -5,6 +5,7 @@ import io.delilaheve.util.ConfigOptions; import org.bukkit.configuration.file.FileConfiguration; import org.jetbrains.annotations.NotNull; import xyz.alexcrea.cuanvil.config.ConfigHolder; +import xyz.alexcrea.cuanvil.util.MetricType; import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil; import xyz.alexcrea.cuanvil.util.config.LoreEditType; @@ -18,6 +19,9 @@ public class PluginSetDefault { int nbSet = 0; + nbSet += trySetDefault(config, METRIC_TYPE, MetricType.AUTO.getValue()); + nbSet += trySetDefault(config, METRIC_COLLECT_ERROR, true); + nbSet += trySetDefault(config, CAP_ANVIL_COST, DEFAULT_CAP_ANVIL_COST); nbSet += trySetDefault(config, MAX_ANVIL_COST, DEFAULT_MAX_ANVIL_COST); nbSet += trySetDefault(config, REMOVE_ANVIL_COST_LIMIT, DEFAULT_REMOVE_ANVIL_COST_LIMIT); @@ -30,7 +34,7 @@ public class PluginSetDefault { nbSet += trySetDefault(config, ALLOW_HEXADECIMAL_COLOR, DEFAULT_ALLOW_HEXADECIMAL_COLOR); nbSet += trySetDefault(config, PERMISSION_NEEDED_FOR_COLOR, DEFAULT_PERMISSION_NEEDED_FOR_COLOR); nbSet += trySetDefault(config, USE_OF_COLOR_COST, DEFAULT_USE_OF_COLOR_COST); - nbSet += trySetDefault(config, DEFAULT_LIMIT_PATH, DEFAULT_ENCHANT_LIMIT); + nbSet += trySetDefault(config, PER_COLOR_CODE_PERMISSION, DEFAULT_PER_COLOR_CODE_PERMISSION); // Lore Edit defaults for (@NotNull LoreEditType value : LoreEditType.values()) { @@ -48,7 +52,6 @@ public class PluginSetDefault { nbSet += trySetDefault(config, path + ALLOW_HEX_COLOR, DEFAULT_ALLOW_HEX_COLOR); nbSet += trySetDefault(config, path + USE_COLOR_COST, DEFAULT_USE_COLOR_COST); } else { - nbSet += trySetDefault(config, path + REMOVE_COLOR_ON_LORE_REMOVE, DEFAULT_REMOVE_COLOR_ON_LORE_REMOVE); nbSet += trySetDefault(config, path + REMOVE_COLOR_COST, DEFAULT_REMOVE_COLOR_COST); } } @@ -58,6 +61,11 @@ public class PluginSetDefault { nbSet += trySetDefault(config, PAPER_EDIT_ORDER, DEFAULT_PAPER_EDIT_ORDER); + nbSet += trySetDefault(config, DIALOG_RENAME_ENABLED, DEFAULT_DIALOG_RENAME_ENABLED); + nbSet += trySetDefault(config, DIALOG_MAX_SIZE, DEFAULT_DIALOG_MAX_SIZE); + nbSet += trySetDefault(config, DIALOG_RENAME_USE_PERMISSION, DEFAULT_DIALOG_RENAME_USE_PERMISSION); + nbSet += trySetDefault(config, DIALOG_KEEP_USER_TEXT, DEFAULT_DIALOG_KEEP_USER_TEXT); + if (nbSet > 0) { CustomAnvil.instance.getLogger().info("Adding " + nbSet + " absent default config values."); ConfigHolder.DEFAULT_CONFIG.saveToDisk(true); diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/UpdateHandler.java b/src/main/java/xyz/alexcrea/cuanvil/update/UpdateHandler.java new file mode 100644 index 0000000..660accb --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/update/UpdateHandler.java @@ -0,0 +1,105 @@ +package xyz.alexcrea.cuanvil.update; + +import io.delilaheve.CustomAnvil; +import xyz.alexcrea.cuanvil.config.ConfigHolder; +import xyz.alexcrea.cuanvil.update.minecraft.MCUpdate; +import xyz.alexcrea.cuanvil.update.minecraft.Update_1_21; +import xyz.alexcrea.cuanvil.update.minecraft.Update_1_21_11; +import xyz.alexcrea.cuanvil.update.minecraft.Update_1_21_9; +import xyz.alexcrea.cuanvil.update.plugin.*; + +import javax.annotation.Nonnull; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; + +public class UpdateHandler { + + private static final String CONFIG_VERSION_PATH = "configVersion"; + + // Handle mc version update then plugin version update + public static void handleUpdates() { + handleMCVersionUpdate(); + handlePluginUpdate(); + } + + private static final Map>> pUpdateMap = Map.of( + new Version(1, 6, 2), PUpdate_1_6_2::handleUpdate, + new Version(1, 6, 7), PUpdate_1_6_7::handleUpdate, + new Version(1, 8, 0), PUpdate_1_8_0::handleUpdate, + new Version(1, 11, 0), PUpdate_1_11_0::handleUpdate, + new Version(1, 15, 5), PUpdate_1_15_5::handleUpdate, + new Version(1, 15, 6), PUpdate_1_15_6::handleUpdate + ); + + private static final List mcUpdateMap = List.of( + new Update_1_21(), + new Update_1_21_9(), + new Update_1_21_11() + ); + + // Handle only plugin update + private static void handlePluginUpdate() { + String versionString = ConfigHolder.DEFAULT_CONFIG.getConfig().getString(CONFIG_VERSION_PATH); + Version current = versionString == null ? new Version(0) : Version.fromString(versionString); + + Set toSave = new HashSet<>(); + + AtomicReference latest = new AtomicReference<>(null); + + // Hopefully, should iterate in the "insertion" order + pUpdateMap.forEach((ver, consumer) -> { + if (ver.greaterThan(current)) { + CustomAnvil.log("handling plugin update to " + ver); + consumer.accept(toSave); + + latest.set(ver); + } + }); + + if (latest.get() != null) { + finishConfiguration(latest.get().toString(), toSave); + } + } + + // Handle minecraft version update (not plugin version update) + public static void handleMCVersionUpdate() { + Version current = UpdateUtils.currentMinecraftVersion(); + + boolean hadUpdate = false; + for (MCUpdate mcUpdate : mcUpdateMap) { + hadUpdate |= mcUpdate.handleUpdate(current, hadUpdate); + } + + if (hadUpdate) { + CustomAnvil.instance.getLogger().info("Updating Done !"); + } + + if(current.major() == 1 && current.minor() < 21) { + var logger = CustomAnvil.instance.getLogger(); + logger.warning("Your are running an old version of minecraft (lower than 1.21)"); + logger.warning("Custom Anvil will stop supporting this version on the first of july 2026"); + } + } + + private static void finishConfiguration(@Nonnull String newVersion, @Nonnull Set toSave) { + CustomAnvil.instance.getLogger().info("Configuration file updated to " + newVersion); + ConfigHolder.DEFAULT_CONFIG.getConfig().set(CONFIG_VERSION_PATH, newVersion); + + toSave.add(ConfigHolder.DEFAULT_CONFIG); + // save + for (ConfigHolder configHolder : toSave) { + configHolder.saveToDisk(true); + } + + // then reload + for (ConfigHolder configHolder : toSave) { + configHolder.reload(); + } + + } + +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/UpdateUtils.java b/src/main/java/xyz/alexcrea/cuanvil/update/UpdateUtils.java index 93d37aa..2907fef 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/update/UpdateUtils.java +++ b/src/main/java/xyz/alexcrea/cuanvil/update/UpdateUtils.java @@ -10,32 +10,27 @@ import java.util.List; public class UpdateUtils { public static final String MINECRAFT_VERSION_PATH = "lowMinecraftVersion"; - public static Version currentMinecraftVersion(){ + public static Version currentMinecraftVersion() { String versionString = Bukkit.getServer().getBukkitVersion().split("-")[0]; return Version.fromString(versionString); } - @Deprecated - public static int[] currentMinecraftVersionArray(){ - String versionString = Bukkit.getServer().getBukkitVersion().split("-")[0]; - return UpdateUtils.readVersionFromString(versionString); - } - - public static int[] readVersionFromString(String versionString){ - String[] partialVersion = versionString.split("\\."); - int[] versionParts = new int[]{0, 0, 0}; - - for (int i = 0; i < Math.min(3, partialVersion.length); i++) { - versionParts[i] = Integer.parseInt(partialVersion[i]); - } - return versionParts; - } - - public static void addToStringList(FileConfiguration config, String path, String... toAdd){ + public static void addToStringList(FileConfiguration config, String path, String... toAdd) { List groups = new ArrayList<>(config.getStringList(path)); groups.addAll(Arrays.asList(toAdd)); config.set(path, groups); } + public static void addAbsentToList(FileConfiguration config, String path, String... toAdd) { + List groups = new ArrayList<>(config.getStringList(path)); + for (String val : toAdd) { + if (groups.contains(val)) continue; + + groups.add(val); + } + config.set(path, groups); + + } + } diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/Update_1_21.java b/src/main/java/xyz/alexcrea/cuanvil/update/Update_1_21.java deleted file mode 100644 index 8180b07..0000000 --- a/src/main/java/xyz/alexcrea/cuanvil/update/Update_1_21.java +++ /dev/null @@ -1,101 +0,0 @@ -package xyz.alexcrea.cuanvil.update; - -import io.delilaheve.CustomAnvil; -import org.bukkit.configuration.file.FileConfiguration; -import xyz.alexcrea.cuanvil.config.ConfigHolder; - -import static xyz.alexcrea.cuanvil.update.UpdateUtils.addToStringList; - -// This is a temporary class that aim to handle 1.21 update. -// It will be replaced by a better system later. -public class Update_1_21 { - - private static final Version V1_21 = new Version(1, 21); - - public static void handleUpdate(){ - // Assume if version path is not null then it's 1.21 - String oldVersion = ConfigHolder.DEFAULT_CONFIG.getConfig().getString(UpdateUtils.MINECRAFT_VERSION_PATH); - if(oldVersion != null){ - Version version = Version.fromString(oldVersion); - - // Test 1.21 - if(V1_21.greaterEqual(version)) return; - } - Version current = UpdateUtils.currentMinecraftVersion(); - - // Test 1.21 - if(current.greaterEqual(V1_21)){ - doUpdate(); - } - - } - - private static void doUpdate() { - CustomAnvil.instance.getLogger().info("Updating config to support 1.21 ..."); - - FileConfiguration baseConfig = ConfigHolder.DEFAULT_CONFIG.getConfig(); - FileConfiguration groupConfig = ConfigHolder.ITEM_GROUP_HOLDER.getConfig(); - FileConfiguration conflictConfig = ConfigHolder.CONFLICT_HOLDER.getConfig(); - FileConfiguration unitConfig = ConfigHolder.UNIT_REPAIR_HOLDER.getConfig(); - - // Add mace to groups - groupConfig.set("mace.type", "include"); - addToStringList(groupConfig, "mace.items", "mace"); - - addToStringList(groupConfig, "can_unbreak.groups", "mace"); - - // Add new enchant conflicts - addToStringList(conflictConfig, "restriction_density.enchantments", "minecraft:density"); - addToStringList(conflictConfig, "restriction_density.notAffectedGroups", "mace", "enchanted_book"); - - addToStringList(conflictConfig, "restriction_breach.enchantments", "minecraft:breach"); - addToStringList(conflictConfig, "restriction_breach.notAffectedGroups", "mace", "enchanted_book"); - - addToStringList(conflictConfig, "restriction_wind_burst.enchantments", "minecraft:wind_burst"); - addToStringList(conflictConfig, "restriction_wind_burst.notAffectedGroups", "mace", "enchanted_book"); - - // Add mace to conflicts - addToStringList(conflictConfig, "restriction_fire_aspect.notAffectedGroups", "mace"); - addToStringList(conflictConfig, "restriction_smite.notAffectedGroups", "mace"); - addToStringList(conflictConfig, "restriction_bane_of_arthropods.notAffectedGroups", "mace"); - - addToStringList(conflictConfig, "mace_enchant_conflict.enchantments", - "minecraft:density", "minecraft:breach", "minecraft:smite", "minecraft:bane_of_arthropods"); - conflictConfig.set("mace_enchant_conflict.maxEnchantmentBeforeConflict", 1); - - // Add level limit - baseConfig.set("enchant_limits.minecraft:density", 5); - baseConfig.set("enchant_limits.minecraft:breach", 4); - baseConfig.set("enchant_limits.minecraft:wind_burst", 3); - - // Add enchant values - baseConfig.set("enchant_values.minecraft:density.item", 2); - baseConfig.set("enchant_values.minecraft:density.book", 1); - - baseConfig.set("enchant_values.minecraft:breach.item", 4); - baseConfig.set("enchant_values.minecraft:breach.book", 2); - - baseConfig.set("enchant_values.minecraft:wind_burst.item", 4); - baseConfig.set("enchant_values.minecraft:wind_burst.book", 2); - - // Add unit repair for mace - unitConfig.set("breeze_rod.mace", 0.25); - - // Set version string as 1.21 - baseConfig.set(UpdateUtils.MINECRAFT_VERSION_PATH, "1.21"); - - // Save - ConfigHolder.DEFAULT_CONFIG.saveToDisk(true); - ConfigHolder.ITEM_GROUP_HOLDER.saveToDisk(true); - ConfigHolder.CONFLICT_HOLDER.saveToDisk(true); - ConfigHolder.UNIT_REPAIR_HOLDER.saveToDisk(true); - - // imply reload of CONFLICT_HOLDER - // We also do not need to reload base config as there is no object related to it. - ConfigHolder.ITEM_GROUP_HOLDER.reload(); - - CustomAnvil.instance.getLogger().info("Updating Done !"); - - } - -} diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/Version.java b/src/main/java/xyz/alexcrea/cuanvil/update/Version.java index ffa8660..a49fbdd 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/update/Version.java +++ b/src/main/java/xyz/alexcrea/cuanvil/update/Version.java @@ -1,6 +1,9 @@ package xyz.alexcrea.cuanvil.update; +import org.jetbrains.annotations.NotNull; + import javax.annotation.Nonnull; +import javax.annotation.Nullable; public record Version(int major, int minor, int patch) { @@ -11,12 +14,18 @@ public record Version(int major, int minor, int patch) { this(major, 0, 0); } - public static Version fromString(@Nonnull String versionString){ + public static Version fromString(@Nullable String versionString){ + if(versionString == null) return new Version(0, 0, 0); + String[] partialVersion = versionString.split("\\."); int[] versionParts = new int[]{0, 0, 0}; for (int i = 0; i < Math.min(3, partialVersion.length); i++) { - versionParts[i] = Integer.parseInt(partialVersion[i]); + try { + versionParts[i] = Integer.parseInt(partialVersion[i]); + } catch (NumberFormatException e) { + break; + } } return new Version(versionParts[0], versionParts[1], versionParts[2]); } @@ -45,4 +54,9 @@ public record Version(int major, int minor, int patch) { this.patch <= other.patch))); } + @NotNull + @Override + public String toString() { + return major + "." + minor + "." + patch; + } } diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/MCUpdate.java b/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/MCUpdate.java new file mode 100644 index 0000000..40fc587 --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/MCUpdate.java @@ -0,0 +1,38 @@ +package xyz.alexcrea.cuanvil.update.minecraft; + +import io.delilaheve.CustomAnvil; +import xyz.alexcrea.cuanvil.config.ConfigHolder; +import xyz.alexcrea.cuanvil.update.UpdateUtils; +import xyz.alexcrea.cuanvil.update.Version; + +public abstract class MCUpdate { + + public final Version version; + + public MCUpdate(Version version){ + this.version = version; + } + + public boolean handleUpdate(Version current, boolean hadUpdate){ + // Test if we are running in this update version or better + if(version.greaterThan(current)) + return false; + + // if version path is not null then check if its it's before this update version + String oldVersion = ConfigHolder.DEFAULT_CONFIG.getConfig().getString(UpdateUtils.MINECRAFT_VERSION_PATH); + if(oldVersion != null){ + var version = Version.fromString(oldVersion); + if(this.version.lesserEqual(version)) return false; + } + + if(!hadUpdate){ + CustomAnvil.instance.getLogger().info("Updating config to support minecraft " + current +" ..."); + } + doUpdate(); + return true; + } + + protected abstract void doUpdate(); + + +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21.java b/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21.java new file mode 100644 index 0000000..3aa6073 --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21.java @@ -0,0 +1,79 @@ +package xyz.alexcrea.cuanvil.update.minecraft; + +import io.delilaheve.CustomAnvil; +import xyz.alexcrea.cuanvil.config.ConfigHolder; +import xyz.alexcrea.cuanvil.update.UpdateUtils; +import xyz.alexcrea.cuanvil.update.Version; + +import static xyz.alexcrea.cuanvil.update.UpdateUtils.addAbsentToList; + +public class Update_1_21 extends MCUpdate { + + public Update_1_21() { + super(new Version(1, 21)); + } + + @Override + protected void doUpdate() { + var baseConfig = ConfigHolder.DEFAULT_CONFIG.getConfig(); + var groupConfig = ConfigHolder.ITEM_GROUP_HOLDER.getConfig(); + var conflictConfig = ConfigHolder.CONFLICT_HOLDER.getConfig(); + var unitConfig = ConfigHolder.UNIT_REPAIR_HOLDER.getConfig(); + + // Add mace to groups + groupConfig.set("mace.type", "include"); + addAbsentToList(groupConfig, "mace.items", "mace"); + + addAbsentToList(groupConfig, "can_unbreak.groups", "mace"); + + // Add new enchant conflicts + addAbsentToList(conflictConfig, "restriction_density.enchantments", "minecraft:density"); + addAbsentToList(conflictConfig, "restriction_density.notAffectedGroups", "mace", "enchanted_book"); + + addAbsentToList(conflictConfig, "restriction_breach.enchantments", "minecraft:breach"); + addAbsentToList(conflictConfig, "restriction_breach.notAffectedGroups", "mace", "enchanted_book"); + + addAbsentToList(conflictConfig, "restriction_wind_burst.enchantments", "minecraft:wind_burst"); + addAbsentToList(conflictConfig, "restriction_wind_burst.notAffectedGroups", "mace", "enchanted_book"); + + // Add mace to conflicts + addAbsentToList(conflictConfig, "restriction_fire_aspect.notAffectedGroups", "mace"); + addAbsentToList(conflictConfig, "restriction_smite.notAffectedGroups", "mace"); + addAbsentToList(conflictConfig, "restriction_bane_of_arthropods.notAffectedGroups", "mace"); + + addAbsentToList(conflictConfig, "sword_enchant_conflict.enchantments", + "minecraft:density", "minecraft:breach"); + + // Add level limit + baseConfig.set("enchant_limits.minecraft:density", 5); + baseConfig.set("enchant_limits.minecraft:breach", 4); + baseConfig.set("enchant_limits.minecraft:wind_burst", 3); + + // Add enchant values + baseConfig.set("enchant_values.minecraft:density.item", 2); + baseConfig.set("enchant_values.minecraft:density.book", 1); + + baseConfig.set("enchant_values.minecraft:breach.item", 4); + baseConfig.set("enchant_values.minecraft:breach.book", 2); + + baseConfig.set("enchant_values.minecraft:wind_burst.item", 4); + baseConfig.set("enchant_values.minecraft:wind_burst.book", 2); + + // Add unit repair for mace + unitConfig.set("breeze_rod.mace", 0.25); + + // Set version string as current + baseConfig.set(UpdateUtils.MINECRAFT_VERSION_PATH, version.toString()); + + // Save + ConfigHolder.DEFAULT_CONFIG.saveToDisk(true); + ConfigHolder.ITEM_GROUP_HOLDER.saveToDisk(true); + ConfigHolder.CONFLICT_HOLDER.saveToDisk(true); + ConfigHolder.UNIT_REPAIR_HOLDER.saveToDisk(true); + + // imply reload of CONFLICT_HOLDER + // We also do not need to reload base config as there is no object related to it. + ConfigHolder.ITEM_GROUP_HOLDER.reload(); + } + +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21_11.java b/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21_11.java new file mode 100644 index 0000000..d639596 --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21_11.java @@ -0,0 +1,84 @@ +package xyz.alexcrea.cuanvil.update.minecraft; + +import xyz.alexcrea.cuanvil.config.ConfigHolder; +import xyz.alexcrea.cuanvil.update.UpdateUtils; +import xyz.alexcrea.cuanvil.update.Version; + +import static xyz.alexcrea.cuanvil.update.UpdateUtils.addAbsentToList; + +public class Update_1_21_11 extends MCUpdate{ + + public Update_1_21_11() { + super(new Version(1, 21, 11)); + } + + @Override + protected void doUpdate() { + var baseConfig = ConfigHolder.DEFAULT_CONFIG.getConfig(); + var groupConfig = ConfigHolder.ITEM_GROUP_HOLDER.getConfig(); + var conflictConfig = ConfigHolder.CONFLICT_HOLDER.getConfig(); + var unitConfig = ConfigHolder.UNIT_REPAIR_HOLDER.getConfig(); + + // Create spear group + groupConfig.set("spears.type", "include"); + addAbsentToList(groupConfig, "spears.items", + "wooden_spear", + "golden_spear", + "stone_spear", + "copper_spear", + "iron_spear", + "diamond_spear", + "netherite_spear"); + + // Add spear group to super group and enchantments + addAbsentToList(groupConfig, "melee_weapons.groups", "spears"); + + addAbsentToList(conflictConfig, "restriction_looting.notAffectedGroups", "spears"); + addAbsentToList(conflictConfig, "restriction_knockback.notAffectedGroups", "spears"); + addAbsentToList(conflictConfig, "restriction_fire_aspect.notAffectedGroups", "spears"); + + // Unit repair for spears + unitConfig.set("gold_ingot.golden_spear", 0.25); + unitConfig.set("copper_ingot.copper_spear", 0.25); + unitConfig.set("iron_ingot.iron_spear", 0.25); + unitConfig.set("diamond.diamond_spear", 0.25); + unitConfig.set("netherite_ingot.netherite_spear", 0.25); + + unitConfig.set("cobblestone.stone_spear", 0.25); + unitConfig.set("cobbled_deepslate.stone_spear", 0.25); + + unitConfig.set("oak_planks.wooden_spear", 0.25); + unitConfig.set("spruce_planks.wooden_spear", 0.25); + unitConfig.set("birch_planks.wooden_spear", 0.25); + unitConfig.set("jungle_planks.wooden_spear", 0.25); + unitConfig.set("acacia_planks.wooden_spear", 0.25); + unitConfig.set("dark_oak_planks.wooden_spear", 0.25); + unitConfig.set("mangrove_planks.wooden_spear", 0.25); + unitConfig.set("cherry_planks.wooden_spear", 0.25); + unitConfig.set("bamboo_planks.wooden_spear", 0.25); + unitConfig.set("crimson_planks.wooden_spear", 0.25); + unitConfig.set("warped_planks.wooden_spear", 0.25); + + // Create lunge enchant value and group + baseConfig.set("enchant_limits.minecraft:lunge", 3); + baseConfig.set("enchant_values.minecraft:lunge.item", 2); + baseConfig.set("enchant_values.minecraft:lunge.book", 1); + + addAbsentToList(conflictConfig, "restriction_lunge.enchantments", "minecraft:lunge"); + addAbsentToList(conflictConfig, "restriction_lunge.notAffectedGroups", "spears", "enchanted_book"); + + // Set version string as current + baseConfig.set(UpdateUtils.MINECRAFT_VERSION_PATH, version.toString()); + + // Save + ConfigHolder.DEFAULT_CONFIG.saveToDisk(true); + ConfigHolder.ITEM_GROUP_HOLDER.saveToDisk(true); + ConfigHolder.CONFLICT_HOLDER.saveToDisk(true); + ConfigHolder.UNIT_REPAIR_HOLDER.saveToDisk(true); + + // imply reload of CONFLICT_HOLDER + // We also do not need to reload base config as there is no object related to it. + ConfigHolder.ITEM_GROUP_HOLDER.reload(); + } + +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21_9.java b/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21_9.java new file mode 100644 index 0000000..d289b9b --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21_9.java @@ -0,0 +1,64 @@ +package xyz.alexcrea.cuanvil.update.minecraft; + +import org.bukkit.configuration.file.FileConfiguration; +import xyz.alexcrea.cuanvil.config.ConfigHolder; +import xyz.alexcrea.cuanvil.update.UpdateUtils; +import xyz.alexcrea.cuanvil.update.Version; + +import static xyz.alexcrea.cuanvil.update.UpdateUtils.addAbsentToList; + +public class Update_1_21_9 extends MCUpdate{ + + public Update_1_21_9() { + super(new Version(1, 21, 9)); + } + + @Override + protected void doUpdate() { + var baseConfig = ConfigHolder.DEFAULT_CONFIG.getConfig(); + var groupConfig = ConfigHolder.ITEM_GROUP_HOLDER.getConfig(); + var unitConfig = ConfigHolder.UNIT_REPAIR_HOLDER.getConfig(); + + // Add cooper items to groups + addAbsentToList(groupConfig, "helmets.items", "copper_helmet"); + addAbsentToList(groupConfig, "chestplate.items", "copper_chestplate"); + addAbsentToList(groupConfig, "leggings.items", "copper_leggings"); + addAbsentToList(groupConfig, "boots.items", "copper_boots"); + + addAbsentToList(groupConfig, "pickaxes.items", "copper_pickaxe"); + addAbsentToList(groupConfig, "shovels.items", "copper_shovel"); + addAbsentToList(groupConfig, "hoes.items", "copper_hoe"); + addAbsentToList(groupConfig, "axes.items", "copper_axe"); + addAbsentToList(groupConfig, "swords.items", "copper_sword"); + + // Add unit repair + addCopperUnitRepair(unitConfig); + + // Set version string as current + baseConfig.set(UpdateUtils.MINECRAFT_VERSION_PATH, version.toString()); + + // Save + ConfigHolder.DEFAULT_CONFIG.saveToDisk(true); + ConfigHolder.ITEM_GROUP_HOLDER.saveToDisk(true); + ConfigHolder.UNIT_REPAIR_HOLDER.saveToDisk(true); + + // imply reload of CONFLICT_HOLDER + // We also do not need to reload base config as there is no object related to it. + ConfigHolder.ITEM_GROUP_HOLDER.reload(); + } + + public static void addCopperUnitRepair(FileConfiguration unitConfig) { + // Add unit repair + unitConfig.set("copper_ingot.copper_helmet", 0.25); + unitConfig.set("copper_ingot.copper_chestplate", 0.25); + unitConfig.set("copper_ingot.copper_leggings", 0.25); + unitConfig.set("copper_ingot.copper_boots", 0.25); + + unitConfig.set("copper_ingot.copper_pickaxe", 0.25); + unitConfig.set("copper_ingot.copper_shovel", 0.25); + unitConfig.set("copper_ingot.copper_hoe", 0.25); + unitConfig.set("copper_ingot.copper_axe", 0.25); + unitConfig.set("copper_ingot.copper_sword", 0.25); + } + +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_11_0.java b/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_11_0.java new file mode 100644 index 0000000..9740971 --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_11_0.java @@ -0,0 +1,137 @@ +package xyz.alexcrea.cuanvil.update.plugin; + +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import xyz.alexcrea.cuanvil.api.MaterialGroupApi; +import xyz.alexcrea.cuanvil.config.ConfigHolder; +import xyz.alexcrea.cuanvil.group.AbstractMaterialGroup; +import xyz.alexcrea.cuanvil.group.IncludeGroup; + +import javax.annotation.Nonnull; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +import static xyz.alexcrea.cuanvil.update.UpdateUtils.addAbsentToList; + +public class PUpdate_1_11_0 { + + private static final List mace_expected = List.of( + "density", + "breach", + "smite", + "bane_of_arthropods" + ); + private static final List sword_expected = List.of( + "sharpness", + "smite", + "bane_of_arthropods" + ); + + private static final Material[] PICKAXES = new Material[]{ + Material.WOODEN_PICKAXE, Material.STONE_PICKAXE, + Material.IRON_PICKAXE, Material.DIAMOND_PICKAXE, + Material.GOLDEN_PICKAXE, Material.NETHERITE_PICKAXE + }; + + private static final Material[] SHOVELS = new Material[]{ + Material.WOODEN_SHOVEL, Material.STONE_SHOVEL, + Material.IRON_SHOVEL, Material.DIAMOND_SHOVEL, + Material.GOLDEN_SHOVEL, Material.NETHERITE_SHOVEL + }; + + private static final Material[] HOES = new Material[]{ + Material.WOODEN_HOE, Material.STONE_HOE, + Material.IRON_HOE, Material.DIAMOND_HOE, + Material.GOLDEN_HOE, Material.NETHERITE_HOE + }; + + public static void handleUpdate(@Nonnull Set toSave) { + handleToolsMigration(); + handleMaceMigration(toSave); + } + + private static void handleToolsMigration() { + // We migrate the mace conflict if exist and unmodified + AbstractMaterialGroup tools = MaterialGroupApi.getGroup("tools"); + + migrateTools(tools, "pickaxes", PICKAXES); + migrateTools(tools, "shovels", SHOVELS); + migrateTools(tools, "hoes", HOES); + } + + private static void migrateTools( + @Nullable AbstractMaterialGroup tools, + @NotNull String toolset, + @NotNull Material[] toolMats) { + + // Create new group + IncludeGroup group = new IncludeGroup(toolset); + NamespacedKey[] keys = new NamespacedKey[toolMats.length]; + for (int i = 0; i < toolMats.length; i++) { + keys[i] = toolMats[i].getKey(); + } + + group.addAll(keys); + + MaterialGroupApi.addMaterialGroup(group, true); + + // Try to see if all the materials was in the tools group. and if so, replace it with the new group + if (tools == null) return; + if (!(tools instanceof IncludeGroup include)) return; + + List mats = List.of(keys); + Set matSet = include.getNonGroupInheritedMaterials(); + if (!matSet.containsAll(mats)) return; + + mats.forEach(matSet::remove); + tools.addToPolicy(group); + MaterialGroupApi.writeMaterialGroup(tools); + } + + private static void handleMaceMigration(@Nonnull Set toSave) { + // We migrate the mace conflict if exist and unmodified + FileConfiguration config = ConfigHolder.CONFLICT_HOLDER.getConfig(); + + if (!config.isConfigurationSection("sword_enchant_conflict")) return; + if (!config.isConfigurationSection("mace_enchant_conflict")) return; + + ConfigurationSection mace_conflict = config.getConfigurationSection("mace_enchant_conflict"); + // Test mace conflict if default + if (mace_conflict == null) return; + if (mace_conflict.getInt("maxEnchantmentBeforeConflict", 0) != 1) return; + + if (mace_conflict.isList("notAffectedGroups") && !mace_conflict.getList("notAffectedGroups").isEmpty()) return; + + List enchantments = mace_conflict.getStringList("enchantments"); + if (enchantments.size() != 4) return; + for (String ench : mace_expected) { + if (!enchantments.contains(ench) && !enchantments.contains("minecraft:" + ench)) return; + } + + // Test sword_enchant_conflict is default + ConfigurationSection sword_conflict = config.getConfigurationSection("sword_enchant_conflict"); + if (sword_conflict.getInt("maxEnchantmentBeforeConflict", 0) != 1) return; + + if (sword_conflict.isList("notAffectedGroups") && !sword_conflict.getList("notAffectedGroups").isEmpty()) + return; + + enchantments = sword_conflict.getStringList("enchantments"); + if (enchantments.size() != 3) return; + for (String ench : sword_expected) { + if (!enchantments.contains(ench) && !enchantments.contains("minecraft:" + ench)) return; + } + + // Finally we know both conflict are default. so we fix + addAbsentToList(config, "sword_enchant_conflict.enchantments", + "minecraft:density", "minecraft:breach"); + + config.set("mace_enchant_conflict", null); + toSave.add(ConfigHolder.CONFLICT_HOLDER); + } + +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_15_5.java b/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_15_5.java new file mode 100644 index 0000000..76f51af --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_15_5.java @@ -0,0 +1,27 @@ +package xyz.alexcrea.cuanvil.update.plugin; + +import org.bukkit.configuration.file.FileConfiguration; +import xyz.alexcrea.cuanvil.config.ConfigHolder; + +import javax.annotation.Nonnull; +import java.util.Set; + +import static xyz.alexcrea.cuanvil.update.UpdateUtils.addAbsentToList; + +public class PUpdate_1_15_5 { + + public static void handleUpdate(@Nonnull Set toSave) { + FileConfiguration config = ConfigHolder.CONFLICT_HOLDER.getConfig(); + + if (config.isConfigurationSection("restriction_luck_of_the_sea")) return; + + // We fix the luck of the see enchantment + addAbsentToList(config, "restriction_luck_of_the_sea.enchantments", + "minecraft:luck_of_the_sea"); + addAbsentToList(config, "restriction_luck_of_the_sea.notAffectedGroups", + "enchanted_book", "fishing_rod"); + + toSave.add(ConfigHolder.CONFLICT_HOLDER); + } + +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_15_6.java b/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_15_6.java new file mode 100644 index 0000000..fde7cfc --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_15_6.java @@ -0,0 +1,27 @@ +package xyz.alexcrea.cuanvil.update.plugin; + +import org.bukkit.configuration.file.FileConfiguration; +import xyz.alexcrea.cuanvil.config.ConfigHolder; +import xyz.alexcrea.cuanvil.update.UpdateUtils; +import xyz.alexcrea.cuanvil.update.Version; +import xyz.alexcrea.cuanvil.update.minecraft.Update_1_21_9; + +import javax.annotation.Nonnull; +import java.util.Set; + +public class PUpdate_1_15_6 { + + public static void handleUpdate(@Nonnull Set toSave) { + // fix only needed for 1.21.9 and above + Version current = UpdateUtils.currentMinecraftVersion(); + if (new Version(1, 21, 9).greaterThan(current)) return; + + FileConfiguration unitConfig = ConfigHolder.UNIT_REPAIR_HOLDER.getConfig(); + + // Add unit repair + Update_1_21_9.addCopperUnitRepair(unitConfig); + + toSave.add(ConfigHolder.UNIT_REPAIR_HOLDER); + } + +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_6_2.java b/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_6_2.java index d004d2d..a4078de 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_6_2.java +++ b/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_6_2.java @@ -1,13 +1,12 @@ package xyz.alexcrea.cuanvil.update.plugin; -import io.delilaheve.CustomAnvil; import org.bukkit.configuration.file.FileConfiguration; import xyz.alexcrea.cuanvil.config.ConfigHolder; import javax.annotation.Nonnull; import java.util.Set; -import static xyz.alexcrea.cuanvil.update.UpdateUtils.addToStringList; +import static xyz.alexcrea.cuanvil.update.UpdateUtils.addAbsentToList; public class PUpdate_1_6_2 { @@ -30,7 +29,7 @@ public class PUpdate_1_6_2 { } if(!contained){ - addToStringList(config, path, "enchanted_book"); + addAbsentToList(config, path, "enchanted_book"); conflictUpdated = true; } } diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_8_0.java b/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_8_0.java index 7685f92..81ce1aa 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_8_0.java +++ b/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_8_0.java @@ -2,10 +2,10 @@ package xyz.alexcrea.cuanvil.update.plugin; import io.delilaheve.util.ConfigOptions; import org.bukkit.configuration.file.FileConfiguration; +import xyz.alexcrea.cuanvil.anvil.AnvilUseType; import xyz.alexcrea.cuanvil.config.ConfigHolder; import xyz.alexcrea.cuanvil.config.WorkPenaltyType; import xyz.alexcrea.cuanvil.gui.config.settings.WorkPenaltyTypeSettingGui; -import xyz.alexcrea.cuanvil.util.AnvilUseType; import javax.annotation.Nonnull; import java.util.EnumMap; diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PluginUpdates.java b/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PluginUpdates.java deleted file mode 100644 index 548c6d1..0000000 --- a/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PluginUpdates.java +++ /dev/null @@ -1,49 +0,0 @@ -package xyz.alexcrea.cuanvil.update.plugin; - -import io.delilaheve.CustomAnvil; -import xyz.alexcrea.cuanvil.config.ConfigHolder; -import xyz.alexcrea.cuanvil.update.Version; - -import javax.annotation.Nonnull; -import java.util.HashSet; -import java.util.Set; - -public class PluginUpdates { - - private static final String CONFIG_VERSION_PATH = "configVersion"; - - public static void handlePluginUpdate() { - String versionString = ConfigHolder.DEFAULT_CONFIG.getConfig().getString(CONFIG_VERSION_PATH); - Version current = versionString == null ? new Version(0) : Version.fromString(versionString); - - Set toSave = new HashSet<>(); - - if (new Version(1, 6, 2).greaterThan(current)) { - PUpdate_1_6_2.handleUpdate(toSave); - - // We assume 1.6.7 will run. TODO a better system instead of that I guess - } - if (new Version(1, 6, 7).greaterThan(current)) { - PUpdate_1_6_7.handleUpdate(toSave); - - // We assume 1.8.0 will run. - } - if (new Version(1, 8, 0).greaterThan(current)) { - PUpdate_1_8_0.handleUpdate(toSave); - - finishConfiguration("1.8.0", toSave); - } - - } - - private static void finishConfiguration(@Nonnull String newVersion, @Nonnull Set toSave) { - CustomAnvil.instance.getLogger().info("Configuration file updated to " + newVersion); - ConfigHolder.DEFAULT_CONFIG.getConfig().set(CONFIG_VERSION_PATH, newVersion); - - toSave.add(ConfigHolder.DEFAULT_CONFIG); - for (ConfigHolder configHolder : toSave) { - configHolder.saveToDisk(true); - } - } - -} diff --git a/src/main/kotlin/io/delilaheve/CustomAnvil.kt b/src/main/kotlin/io/delilaheve/CustomAnvil.kt index 442af0a..4d99faf 100644 --- a/src/main/kotlin/io/delilaheve/CustomAnvil.kt +++ b/src/main/kotlin/io/delilaheve/CustomAnvil.kt @@ -6,10 +6,14 @@ import org.bukkit.configuration.file.YamlConfiguration import org.bukkit.plugin.java.JavaPlugin import xyz.alexcrea.cuanvil.api.event.CAConfigReadyEvent import xyz.alexcrea.cuanvil.api.event.CAEnchantRegistryReadyEvent +import xyz.alexcrea.cuanvil.command.CustomAnvilCommand import xyz.alexcrea.cuanvil.command.EditConfigExecutor import xyz.alexcrea.cuanvil.command.ReloadExecutor import xyz.alexcrea.cuanvil.config.ConfigHolder import xyz.alexcrea.cuanvil.dependency.DependencyManager +import xyz.alexcrea.cuanvil.dependency.MinecraftVersionUtil +import xyz.alexcrea.cuanvil.dependency.economy.EconomyManager +import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil import xyz.alexcrea.cuanvil.enchant.CAEnchantmentRegistry import xyz.alexcrea.cuanvil.gui.config.MainConfigGui import xyz.alexcrea.cuanvil.gui.util.GuiSharedConstant @@ -17,10 +21,10 @@ import xyz.alexcrea.cuanvil.listener.AnvilCloseListener import xyz.alexcrea.cuanvil.listener.AnvilResultListener import xyz.alexcrea.cuanvil.listener.ChatEventListener import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener +import xyz.alexcrea.cuanvil.update.ModrinthUpdateChecker import xyz.alexcrea.cuanvil.update.PluginSetDefault -import xyz.alexcrea.cuanvil.update.Update_1_21 -import xyz.alexcrea.cuanvil.update.plugin.PluginUpdates -import xyz.alexcrea.cuanvil.util.Metrics +import xyz.alexcrea.cuanvil.update.UpdateHandler +import xyz.alexcrea.cuanvil.util.MetricsUtil import java.io.File import java.io.FileReader import java.util.logging.Level @@ -31,8 +35,8 @@ import java.util.logging.Level open class CustomAnvil : JavaPlugin() { companion object { - // bstats plugin id - private const val bstatsPluginId = 20923 + // pluginIDS + private const val modrinthPluginID = "S75Ueiq9" // Permission string required to use the plugin's features const val affectedByPluginPermission = "ca.affected" @@ -46,14 +50,18 @@ open class CustomAnvil : JavaPlugin() { // Permission string required to reload the config const val commandReloadPermission = "ca.command.reload" + // Permission string required to get diagnostic data + const val diagnosticPermission = "ca.command.diagnostic" + // Permission string required to edit the plugin's config const val editConfigPermission = "ca.config.edit" + // Command Name to reload the config const val commandReloadName = "anvilconfigreload" - // Test command name - const val commandTestName = "customanvilconfig" + // Config command name + const val commandConfigName = "customanvilconfig" // Current plugin instance lateinit var instance: CustomAnvil @@ -61,10 +69,12 @@ open class CustomAnvil : JavaPlugin() { // Chat message listener lateinit var chatListener: ChatEventListener + var latestVer: String? = null + /** * Logging handler */ - fun log(message: String) { + @JvmStatic fun log(message: String) { if (ConfigOptions.debugLog) { instance.logger.info(message) } @@ -79,7 +89,26 @@ open class CustomAnvil : JavaPlugin() { } } + } + // stop plugin if we do not force a dirty start (true by default) + // Return true if start was stopped + private fun tryDirtyStart(): Boolean { + if(!ConfigHolder.DEFAULT_CONFIG.config.getBoolean("dirty_start", false)) { + Bukkit.getPluginManager().disablePlugin(this) + return true + } + return false + } + + // stop plugin if we force a safe start (false by default) + // Return true if start was stopped + private fun trySafeStart(): Boolean { + if(ConfigHolder.DEFAULT_CONFIG.config.getBoolean("safe_start", false)) { + Bukkit.getPluginManager().disablePlugin(this) + return true + } + return false } /** @@ -87,7 +116,74 @@ open class CustomAnvil : JavaPlugin() { */ override fun onEnable() { instance = this + try { + legacyCheck() + } catch (e: Exception) { + logger.log(Level.SEVERE, "error trying to check for legacy system", e) + MetricsUtil.trackError(e) + if(trySafeStart()) return + } + // Add commands + try { + prepareCommand() + } catch (e: Exception) { + logger.log(Level.SEVERE, "error trying to register commands", e) + MetricsUtil.trackError(e) + if(trySafeStart()) return + } + + // Load default configuration + try { + if(!ConfigHolder.loadDefaultConfig()) + throw RuntimeException("Error loading configuration file") + } catch (e: Exception) { + logger.log(Level.SEVERE, "error occurred loading default configuration", e) + MetricsUtil.trackError(e) + if(tryDirtyStart()) return + } + + // Load dependency + try { + DependencyManager.loadDependency() + } catch (e: Exception) { + logger.log(Level.SEVERE, "error loading dependency compatibility", e) + MetricsUtil.trackError(e) + if(tryDirtyStart()) return + } + + // Register listeners + try { + registerListeners() + } catch (e: Exception) { + logger.log(Level.SEVERE, "error registering listeners", e) + MetricsUtil.trackError(e) + if(tryDirtyStart()) return + } + + // Load metrics + MetricsUtil.loadMetrics(this) + + // Load other thing later. + // It is so other dependent plugins can implement there event listener before we fire them. + DependencyManager.scheduler.scheduleGlobally(this) { loadEnchantmentSystemDirty() } + } + + override fun onDisable() { + MetricsUtil.shutdownMetrics() + } + + private fun loadEnchantmentSystemDirty() { + try { + loadEnchantmentSystem() + } catch (e: Exception) { + logger.log(Level.SEVERE, "error initializing enchantment system", e) + MetricsUtil.trackError(e) + tryDirtyStart() + } + } + + private fun legacyCheck() { // Disable old plugin name if exist val potentialPlugin = Bukkit.getPluginManager().getPlugin("UnsafeEnchantsPlus") if (potentialPlugin != null) { @@ -96,33 +192,43 @@ open class CustomAnvil : JavaPlugin() { logger.warning("Please note CustomAnvil is a more recent version of UnsafeEnchantsPlus") } - // Add commands - prepareCommand() - - // Load chat listener - chatListener = ChatEventListener() - server.pluginManager.registerEvents(chatListener, this) - - // Load default configuration - if (!ConfigHolder.loadDefaultConfig()) { - logger.log(Level.SEVERE,"could not load default config.") - return + val isPaper = PlatformUtil.isPaper + if(!isPaper) { + logger.warning("It seems you are using spigot") + logger.warning("Please take notice that spigot is less supported than paper and derivatives") + if(MinecraftVersionUtil.isTooNewForSpigot) { + logger.warning("If replace too expensive is not working this is likely because of spigot") + logger.warning("As native nms is not supported for spigot starting 26.1") + } } - // Load dependency - DependencyManager.loadDependency() + val loader = if(isPaper) "paper" else "spigot" + + val version = description.version + val featured = if(version.contains("dev")) null else true + + ModrinthUpdateChecker(modrinthPluginID, loader, null) + .setFeatured(featured) + .setOnError { + logger.log(Level.WARNING, "error trying to fetch latest update", it) + } + .checkVersion { latestVer: String? -> + CustomAnvil.latestVer = latestVer + if(latestVer == null || version.contains(latestVer)) return@checkVersion + + logger.warning("An update may be available: $latestVer") + } + } + + private fun registerListeners() { + // Register chat listener + chatListener = ChatEventListener() + server.pluginManager.registerEvents(chatListener, this) // Register anvil events server.pluginManager.registerEvents(PrepareAnvilListener(), this) server.pluginManager.registerEvents(AnvilResultListener(), this) server.pluginManager.registerEvents(AnvilCloseListener(DependencyManager.packetManager), this) - - // Load metrics - Metrics(this, bstatsPluginId) - - // Load other thing later. - // It is so other dependent plugins can implement there event listener before we fire them. - DependencyManager.scheduler.scheduleGlobally(this, {loadEnchantmentSystem()}) } private fun loadEnchantmentSystem(){ @@ -135,15 +241,13 @@ open class CustomAnvil : JavaPlugin() { // Load config if (!ConfigHolder.loadNonDefaultConfig()) { - logger.log(Level.SEVERE,"could not load non default config.") + logger.log(Level.SEVERE,"Plugin has an issue while trying to load non default config... exiting...") + server.pluginManager.disablePlugin(this) return } - // temporary: handle 1.21 update - Update_1_21.handleUpdate() - - // plugin configuration updates - PluginUpdates.handlePluginUpdate() + // Handle minecraft and plugin updates + UpdateHandler.handleUpdates() // Register enchantment of compatible plugin and load configuration change. DependencyManager.handleCompatibilityConfig() @@ -156,9 +260,11 @@ open class CustomAnvil : JavaPlugin() { MainConfigGui.getInstance().init(DependencyManager.packetManager) GuiSharedConstant.loadConstants() + // Prepare economy if possible + EconomyManager.setupEconomy(this) + // Finally, re add default we may be missing PluginSetDefault.reAddMissingDefault() - } fun reloadResource( @@ -208,8 +314,10 @@ open class CustomAnvil : JavaPlugin() { var command = getCommand(commandReloadName) command?.setExecutor(ReloadExecutor()) - command = getCommand(commandTestName) + command = getCommand(commandConfigName) command?.setExecutor(EditConfigExecutor()) + + CustomAnvilCommand(this) } } diff --git a/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt b/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt index d9d1f87..9dc85f9 100644 --- a/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt +++ b/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt @@ -2,11 +2,17 @@ package io.delilaheve.util import io.delilaheve.CustomAnvil import io.delilaheve.util.EnchantmentUtil.enchantmentName +import org.bukkit.NamespacedKey +import org.bukkit.entity.HumanEntity +import xyz.alexcrea.cuanvil.anvil.AnvilUseType import xyz.alexcrea.cuanvil.config.ConfigHolder import xyz.alexcrea.cuanvil.config.WorkPenaltyType import xyz.alexcrea.cuanvil.config.WorkPenaltyType.WorkPenaltyPart +import xyz.alexcrea.cuanvil.dependency.DependencyManager +import xyz.alexcrea.cuanvil.dependency.economy.EconomyManager import xyz.alexcrea.cuanvil.enchant.CAEnchantment -import xyz.alexcrea.cuanvil.util.AnvilUseType +import xyz.alexcrea.cuanvil.util.dialog.AnvilRenameDialogUtil +import java.math.BigDecimal import java.util.* /** @@ -18,6 +24,9 @@ object ConfigOptions { // Path for config values // ---------------------- + const val METRIC_TYPE = "metric_type" + const val METRIC_COLLECT_ERROR = "metric_collect_errors" + const val CAP_ANVIL_COST = "limit_repair_cost" const val MAX_ANVIL_COST = "limit_repair_value" const val REMOVE_ANVIL_COST_LIMIT = "remove_repair_limit" @@ -30,13 +39,17 @@ object ConfigOptions { const val ITEM_RENAME_COST = "item_rename_cost" const val SACRIFICE_ILLEGAL_COST = "sacrifice_illegal_enchant_cost" + const val ADD_BOOK_ENCHANTMENT_AS_STORED_ENCHANTMENT = "add_book_enchantment_as_stored_enchantment" // Color related config const val ALLOW_COLOR_CODE = "allow_color_code" const val ALLOW_HEXADECIMAL_COLOR = "allow_hexadecimal_color" + const val ALLOW_MINIMESSAGE = "allow_minimessage" const val PERMISSION_NEEDED_FOR_COLOR = "permission_needed_for_color" const val USE_OF_COLOR_COST = "use_of_color_cost" + const val PER_COLOR_CODE_PERMISSION = "per_color_code_permission" + // Work penalty config const val WORK_PENALTY_ROOT = "work_penalty" const val WORK_PENALTY_INCREASE = "shared_increase" @@ -44,13 +57,31 @@ object ConfigOptions { const val EXCLUSIVE_WORK_PENALTY_INCREASE = "exclusive_increase" const val EXCLUSIVE_WORK_PENALTY_ADDITIVE = "exclusive_additive" - const val DEFAULT_LIMIT_PATH = "default_limit" + // Enchant limit config + const val ENCHANT_COUNT_LIMIT_ROOT = "enchantment_count_limit" + const val ENCHANT_COUNT_LIMIT_DEFAULT = "$ENCHANT_COUNT_LIMIT_ROOT.default" + const val ENCHANT_COUNT_LIMIT_ITEMS = "$ENCHANT_COUNT_LIMIT_ROOT.items" const val ENCHANT_LIMIT_ROOT = "enchant_limits" const val ENCHANT_VALUES_ROOT = "enchant_values" + // Dialog menu rename + const val DIALOG_RENAME_ENABLED = "enable_dialog_rename" + const val DIALOG_MAX_SIZE = "dialog_rename_max_size" + const val DIALOG_RENAME_USE_PERMISSION = "permission_needed_for_dialog_rename" + const val DIALOG_KEEP_USER_TEXT = "dialog_rename_keep_user_text" + + // Others const val DISABLE_MERGE_OVER_ROOT = "disable-merge-over" + const val IMMUTABLE_ENCHANTMENT_LIST = "immutable_enchantments" + + // Monetary configs + const val MONETARY_USAGE_ROOT = "monetary_cost" + const val SHOULD_USE_MONEY = "$MONETARY_USAGE_ROOT.enabled" + const val MONEY_CURRENCY = "$MONETARY_USAGE_ROOT.currency" + const val MONETARY_MULTIPLIER_ROOT = "$MONETARY_USAGE_ROOT.multipliers" + // Keys for specific enchantment values private const val KEY_BOOK = "book" private const val KEY_ITEM = "item" @@ -75,19 +106,34 @@ object ConfigOptions { const val DEFAULT_ITEM_RENAME_COST = 1 const val DEFAULT_SACRIFICE_ILLEGAL_COST = 1 + const val DEFAULT_ADD_BOOK_ENCHANTMENT_AS_STORED_ENCHANTMENT = false + + const val DEFAULT_ENCHANT_COUNT_LIMIT = -1 // Color related config const val DEFAULT_ALLOW_COLOR_CODE = false const val DEFAULT_ALLOW_HEXADECIMAL_COLOR = false + const val DEFAULT_ALLOW_MINIMESSAGE = false const val DEFAULT_PERMISSION_NEEDED_FOR_COLOR = true const val DEFAULT_USE_OF_COLOR_COST = 0 - const val DEFAULT_ENCHANT_LIMIT = 5 + const val DEFAULT_PER_COLOR_CODE_PERMISSION = false + + // Monetary configs + const val DEFAULT_SHOULD_USE_MONEY = false + const val DEFAULT_MONEY_CURRENCY = "default" + const val DEFAULT_MONEY_MULTIPLIER = 1.0 // Debug flag private const val DEFAULT_DEBUG_LOG = false private const val DEFAULT_VERBOSE_DEBUG_LOG = false + // Dialog menu rename + const val DEFAULT_DIALOG_RENAME_ENABLED = false + const val DEFAULT_DIALOG_MAX_SIZE = 256 + const val DEFAULT_DIALOG_RENAME_USE_PERMISSION = false + const val DEFAULT_DIALOG_KEEP_USER_TEXT = true + // ------------- // Config Ranges // ------------- @@ -112,9 +158,15 @@ object ConfigOptions { @JvmField val USE_OF_COLOR_COST_RANGE = 0..1000 - // Valid range for an enchantment limit @JvmField - val ENCHANT_LIMIT_RANGE = 1..255 + val DIALOG_MAX_SIZE_RANGE = 0..Int.MAX_VALUE + + // Valid range for an enchantment limit + const val ENCHANT_LIMIT = 255 + + // Valid range for an enchantment count limit + @JvmField + val ENCHANT_COUNT_LIMIT_RANGE = -1..255 // -------------- // Other defaults @@ -126,6 +178,11 @@ object ConfigOptions { // Default max before merge disabled (negative mean enabled) const val DEFAULT_MAX_BEFORE_MERGE_DISABLED = -1 + // ----------- + // Permissions + // ----------- + private const val RENAME_DIALOG_PERMISSION = "ca.rename.dialog" + // ------------- // Get methods // ------------- @@ -220,6 +277,16 @@ object ConfigOptions { ?: DEFAULT_SACRIFICE_ILLEGAL_COST } + /** + * Consider book enchantment as book stored enchantment + */ + val addBookEnchantmentAsStoredEnchantment : Boolean + get(){ + return ConfigHolder.DEFAULT_CONFIG + .config + .getBoolean(ADD_BOOK_ENCHANTMENT_AS_STORED_ENCHANTMENT, DEFAULT_ADD_BOOK_ENCHANTMENT_AS_STORED_ENCHANTMENT) + } + /** * Allow usage of color code */ @@ -240,12 +307,22 @@ object ConfigOptions { .getBoolean(ALLOW_HEXADECIMAL_COLOR, DEFAULT_ALLOW_HEXADECIMAL_COLOR) } + /** + * Allow usage of minimessage formating + */ + val allowMinimessage: Boolean + get() { + return ConfigHolder.DEFAULT_CONFIG + .config + .getBoolean(ALLOW_MINIMESSAGE, DEFAULT_ALLOW_MINIMESSAGE) + } + /** * If one of the color component is enabled */ val renameColorPossible: Boolean get() { - return allowColorCode || allowHexadecimalColor + return allowColorCode || allowHexadecimalColor || allowMinimessage } /** @@ -258,6 +335,16 @@ object ConfigOptions { .getBoolean(PERMISSION_NEEDED_FOR_COLOR, DEFAULT_PERMISSION_NEEDED_FOR_COLOR) } + /** + * Should each color code require a permission + */ + val usePerColorCodePermission: Boolean + get() { + return ConfigHolder.DEFAULT_CONFIG + .config + .getBoolean(PER_COLOR_CODE_PERMISSION, DEFAULT_PER_COLOR_CODE_PERMISSION) + } + /** * How many xp should use of color should cost */ @@ -305,13 +392,43 @@ object ConfigOptions { } /** - * Default enchantment limit + * Get material enchantment count limit + * + * @return the current enchantment limit. -1 if none */ - private val defaultEnchantLimit: Int + fun getEnchantCountLimit(type: NamespacedKey): Int? { + val limit = materialEnchantCountLimit(type) + + if(limit != null) return limit + if(defaultEnchantCountLimit >= 0) return defaultEnchantCountLimit + + return DependencyManager.ecoEnchantCompatibility?.getEcoLevelLimit() + } + + /** + * Get the material enchantment count limit. + * + * @return The current enchantment limit. -1 if none + */ + private fun materialEnchantCountLimit(type: NamespacedKey): Int? { + val path = "$ENCHANT_COUNT_LIMIT_ITEMS.${type.key.lowercase()}" + if(!ConfigHolder.DEFAULT_CONFIG.config.isInt(path)) + return null + + return ConfigHolder.DEFAULT_CONFIG.config + .getInt(path) + .takeIf { it in ENCHANT_COUNT_LIMIT_RANGE } + } + /** + * User configured default enchantment count limit + */ + val defaultEnchantCountLimit: Int get() { return ConfigHolder.DEFAULT_CONFIG .config - .getInt(DEFAULT_LIMIT_PATH, DEFAULT_ENCHANT_LIMIT) + .getInt(ENCHANT_COUNT_LIMIT_DEFAULT, DEFAULT_ENCHANT_COUNT_LIMIT) + .takeIf { it in ENCHANT_COUNT_LIMIT_RANGE } + ?: DEFAULT_ENCHANT_COUNT_LIMIT } /** @@ -334,46 +451,90 @@ object ConfigOptions { .getBoolean(VERBOSE_DEBUG_LOGGING, DEFAULT_VERBOSE_DEBUG_LOG) } + /** + * Is the dialog menu for rename enabled + */ + val doRenameDialog: Boolean + get() { + return ConfigHolder.DEFAULT_CONFIG + .config + .getBoolean(DIALOG_RENAME_ENABLED, DEFAULT_DIALOG_RENAME_ENABLED) + } + + /** + * Do the dialog menu require permission + */ + val doRenameDialogUsePermission: Boolean + get() { + return ConfigHolder.DEFAULT_CONFIG + .config + .getBoolean(DIALOG_RENAME_USE_PERMISSION, DEFAULT_DIALOG_RENAME_USE_PERMISSION) + } + + fun canUseDialogRename(player: HumanEntity): Boolean { + if(!doRenameDialog || !AnvilRenameDialogUtil.anvilRenameDialog.canSendDialog()) return false + if(doRenameDialogUsePermission && !player.hasPermission(RENAME_DIALOG_PERMISSION)) return false + + return true + } + + /** + * Do the dialog menu require permission + */ + val renameDialogMaxSize: Int + get() { + return ConfigHolder.DEFAULT_CONFIG + .config + .getInt(DIALOG_MAX_SIZE, DEFAULT_DIALOG_MAX_SIZE) + .takeIf { it in DIALOG_MAX_SIZE_RANGE } + ?: Int.MAX_VALUE + } + + /** + * Should the text used for rename should be kept in the item's pdc + */ + val shouldKeepRenameText: Boolean + get() { + return ConfigHolder.DEFAULT_CONFIG + .config + .getBoolean(DIALOG_KEEP_USER_TEXT, DEFAULT_DIALOG_KEEP_USER_TEXT) + } + /** * Get the given [enchantment]'s limit */ fun enchantLimit(enchantment: CAEnchantment): Int { + val limit = rawEnchantLimit(enchantment) + if(limit >= 0) return limit.coerceAtMost(ENCHANT_LIMIT) + + // get default + return enchantment.defaultMaxLevel() + } + + /** + * Get the given [enchantment]'s limit + */ + fun rawEnchantLimit(enchantment: CAEnchantment): Int { // Test namespace var limit = enchantLimit(enchantment.key.toString()) - if (limit != null) return limit + if (limit >= 0) return limit // Test legacy (name only) limit = enchantLimit(enchantment.enchantmentName) - if (limit != null) return limit + if (limit >= 0) return limit - // get default (and test old legacy if present) - return getDefaultLevel(enchantment.enchantmentName) + // Default to negative + return -1 } /** * Get the given [enchantmentName]'s limit */ - private fun enchantLimit(enchantmentName: String): Int? { + private fun enchantLimit(enchantmentName: String): Int { val path = "${ENCHANT_LIMIT_ROOT}.$enchantmentName" - return CustomAnvil.instance - .config - .getInt(path, ENCHANT_LIMIT_RANGE.first - 1) - .takeIf { it in ENCHANT_LIMIT_RANGE } - } - - /** - * Get default value if enchantment do not exist on config - */ - private fun getDefaultLevel( - enchantmentName: String, // compatibility with 1.20.5. TODO better update system - ): Int { - if (enchantmentName == "sweeping_edge") { - val limit = enchantLimit("sweeping") - if (limit != null) return limit - - } - return defaultEnchantLimit + return CustomAnvil.instance.config + .getInt(path, -1) } /** @@ -445,20 +606,20 @@ object ConfigOptions { fun maxBeforeMergeDisabled(enchantment: CAEnchantment): Int { val key = enchantment.key.toString() var value = maxBeforeMergeDisabled(key) - if (value != null) return value + if (value >= 0) return value // Legacy name val legacy = enchantment.enchantmentName value = maxBeforeMergeDisabled(legacy) - if (value != null) return value + if (value >= 0) return value if (key == "minecraft:sweeping_edge") { value = maxBeforeMergeDisabled("minecraft:sweeping") - if (value != null) return value + if (value >= 0) return value // legacy name of legacy enchantment name value = maxBeforeMergeDisabled("sweeping") - if (value != null) return value + if (value >= 0) return value } return DEFAULT_MAX_BEFORE_MERGE_DISABLED @@ -468,14 +629,51 @@ object ConfigOptions { * Get the given [enchantmentName]'s level before merge is disabled * a negative value would mean never disabled */ - private fun maxBeforeMergeDisabled(enchantmentName: String): Int? { + private fun maxBeforeMergeDisabled(enchantmentName: String): Int { // find if set val path = "${DISABLE_MERGE_OVER_ROOT}.$enchantmentName" return CustomAnvil.instance .config - .getInt(path, ENCHANT_LIMIT_RANGE.min() - 1) - .takeIf { it in ENCHANT_LIMIT_RANGE } + .getInt(path, -1) + } + + fun isImmutable(key: NamespacedKey): Boolean { + val immutables = ConfigHolder.DEFAULT_CONFIG.config.getStringList(IMMUTABLE_ENCHANTMENT_LIST) + + // We need to ignore case so can't just check "contain" + for (ench in immutables) { + if (ench.equals(key.toString(), ignoreCase = true) || + ench.equals(key.key, ignoreCase = true) + ) + return true + } + return false + } + + /* + * Monetary configs (only for 1.21.6+) + * Also require dialog rename + */ + fun shouldUseMoney(player: HumanEntity): Boolean { + return EconomyManager.economy?.initialized() == true && + canUseDialogRename(player) && + ConfigHolder.DEFAULT_CONFIG + .config + .getBoolean(SHOULD_USE_MONEY, DEFAULT_SHOULD_USE_MONEY) + } + + val usedCurrency: String + get() { + return ConfigHolder.DEFAULT_CONFIG + .config + .getString(MONEY_CURRENCY, DEFAULT_MONEY_CURRENCY)!! + } + + fun getMonetaryMultiplier(type: String): BigDecimal { + return BigDecimal(ConfigHolder.DEFAULT_CONFIG + .config + .getDouble("$MONETARY_MULTIPLIER_ROOT.$type", DEFAULT_MONEY_MULTIPLIER)) } } diff --git a/src/main/kotlin/io/delilaheve/util/EnchantmentUtil.kt b/src/main/kotlin/io/delilaheve/util/EnchantmentUtil.kt index 87e4bae..af959f2 100644 --- a/src/main/kotlin/io/delilaheve/util/EnchantmentUtil.kt +++ b/src/main/kotlin/io/delilaheve/util/EnchantmentUtil.kt @@ -6,6 +6,7 @@ import org.bukkit.inventory.ItemStack import xyz.alexcrea.cuanvil.config.ConfigHolder import xyz.alexcrea.cuanvil.enchant.CAEnchantment import xyz.alexcrea.cuanvil.group.ConflictType +import xyz.alexcrea.cuanvil.util.MaterialUtil.customType import kotlin.math.max import kotlin.math.min @@ -30,81 +31,97 @@ object EnchantmentUtil { ) = mutableMapOf().apply { putAll(this@combineWith) + CustomAnvil.verboseLog("Testing merge") val bypassFuse = player.hasPermission(CustomAnvil.bypassFusePermission) val bypassLevel = player.hasPermission(CustomAnvil.bypassLevelPermission) - other.forEach { (enchantment, level) -> - if(!enchantment.isAllowed(player)) return@forEach + var maxEnchantCount = ConfigOptions.getEnchantCountLimit(item.customType) + if(maxEnchantCount == null || maxEnchantCount < 0) maxEnchantCount = Int.MAX_VALUE - // Get max level or 255 if player can bypass - val maxLevel = if (bypassLevel) { 255 } + val allowed = other.filter { (enchantment, _) -> enchantment.isAllowed(player) } + val new = allowed.filter{ (enchantment, _) -> !containsKey(enchantment)} + val old = allowed.filter{ (enchantment, _) -> containsKey(enchantment)} + + fun maxLevel(enchantment: CAEnchantment): Int { + val max = if (bypassLevel) { 255 } else { ConfigOptions.enchantLimit(enchantment) } - CustomAnvil.verboseLog("Max level of ${enchantment.key} is $maxLevel (bypassLevel is $bypassLevel)") + CustomAnvil.verboseLog("Max level of ${enchantment.key} is $max (bypassLevel is $bypassLevel)") + return max + } + + old.forEach { (enchantment, level) -> + // Get max level or 255 if player can bypass + val maxLevel = maxLevel(enchantment) val cappedLevel = min(level, maxLevel) - // Enchantment not yet in result list - if (!containsKey(enchantment)) { - // Add the enchantment if it doesn't have conflicts, or if player is allowed to bypass enchantment restrictions - this[enchantment] = cappedLevel - if(bypassFuse){ - CustomAnvil.verboseLog("Bypassed conflict check for ${enchantment.key}") - return@forEach - } - - val conflictType = ConfigHolder.CONFLICT_HOLDER.conflictManager - .isConflicting(this, item, enchantment) - - if (conflictType != ConflictType.NO_CONFLICT) { - CustomAnvil.verboseLog("Enchantment not yet in result list, but there is conflict (${enchantment.key}, conflict: $conflictType)") - this.remove(enchantment) - } + val oldLevel = this[enchantment]!! // <- should not be null. (enchantment already in result list) + // ... and they're not the same level + if (oldLevel != cappedLevel) { + // apply the greater of the two or left one if right is above max + this[enchantment] = max(oldLevel, cappedLevel) } - // Enchantment already in result list + // ... and they're the same level else { - val oldLevel = this[enchantment]!! // <- should not be null. (enchantment already in result list) - - if(bypassFuse){ - CustomAnvil.verboseLog("Bypassed conflict check for ${enchantment.key}") - } else { - val conflictType = ConfigHolder.CONFLICT_HOLDER.conflictManager - .isConflicting(this, item, enchantment) - - // ... and they are conflicting - if(conflictType != ConflictType.NO_CONFLICT){ + // We test if it is allowed to merge at this level + if(!bypassLevel){ + val maxBeforeDisabled = ConfigOptions.maxBeforeMergeDisabled(enchantment) + if((maxBeforeDisabled > 0) && (oldLevel >= maxBeforeDisabled)) { CustomAnvil.verboseLog( - "Enchantment already in result list, and they are conflicting (${enchantment.key}, conflict: $conflictType)") + "Reached max merge before disable for ${enchantment.key}: $oldLevel/$maxBeforeDisabled)") return@forEach } } - // ... and they're not the same level - if (oldLevel != cappedLevel) { - // apply the greater of the two or left one if right is above max - this[enchantment] = max(oldLevel, cappedLevel) + // Now we increase the enchantment level by 1 + var newLevel = oldLevel + 1 + newLevel = max(min(newLevel, maxLevel), oldLevel) + this[enchantment] = newLevel + } - } - // ... and they're the same level - else { - // We test if it is allowed to merge at this level - if(!bypassLevel){ - val maxBeforeDisabled = ConfigOptions.maxBeforeMergeDisabled(enchantment) - if((maxBeforeDisabled > 0) && (oldLevel >= maxBeforeDisabled)) { - CustomAnvil.verboseLog( - "Reached max merge before disable for ${enchantment.key}: $oldLevel/$maxBeforeDisabled)") - return@forEach - } - } - - // Now we increase the enchantment level by 1 - var newLevel = oldLevel + 1 - newLevel = max(min(newLevel, maxLevel), oldLevel) - this[enchantment] = newLevel + if(bypassFuse){ + CustomAnvil.verboseLog("Bypassed conflict check for ${enchantment.key}") + } else { + val conflictType = ConfigHolder.CONFLICT_HOLDER.conflictManager + .isConflicting(this, item, enchantment) + // ... and they are conflicting + if(conflictType != ConflictType.NO_CONFLICT){ + CustomAnvil.verboseLog( + "Enchantment already in result list, and they are conflicting (${enchantment.key}, conflict: $conflictType)") + this[enchantment] = oldLevel + return@forEach } } } + + // Try to add new now + new.forEach { (enchantment, level) -> + // Get max level or 255 if player can bypass + val maxLevel = maxLevel(enchantment) + val cappedLevel = min(level, maxLevel) + + // Do not allow new enchantment if above maximum + if(this.size >= maxEnchantCount) return@forEach + + // Add the enchantment if it doesn't have conflicts, or if player is allowed to bypass enchantment restrictions + this[enchantment] = cappedLevel + if(bypassFuse){ + CustomAnvil.verboseLog("Bypassed conflict check for ${enchantment.key}") + return@forEach + } + + val conflictType = ConfigHolder.CONFLICT_HOLDER.conflictManager + .isConflicting(this, item, enchantment) + + if (conflictType != ConflictType.NO_CONFLICT) { + CustomAnvil.verboseLog("Enchantment not yet in result list, but there is conflict (${enchantment.key}, conflict: $conflictType)") + this.remove(enchantment) + } + + } + } } diff --git a/src/main/kotlin/io/delilaheve/util/ItemUtil.kt b/src/main/kotlin/io/delilaheve/util/ItemUtil.kt index a85af39..25698ad 100644 --- a/src/main/kotlin/io/delilaheve/util/ItemUtil.kt +++ b/src/main/kotlin/io/delilaheve/util/ItemUtil.kt @@ -4,6 +4,9 @@ import org.bukkit.Material.ENCHANTED_BOOK import org.bukkit.inventory.ItemStack import org.bukkit.inventory.meta.Damageable import xyz.alexcrea.cuanvil.enchant.CAEnchantment +import xyz.alexcrea.cuanvil.update.UpdateUtils +import xyz.alexcrea.cuanvil.util.MaterialUtil.customType +import xyz.alexcrea.cuanvil.util.MaxDamageCheckerUtil import kotlin.math.ceil import kotlin.math.max import kotlin.math.min @@ -35,6 +38,13 @@ object ItemUtil { } + private fun maxDamage(damageable: Damageable): Int { + val ver = UpdateUtils.currentMinecraftVersion() + if(ver.major <= 1 && ver.minor <= 20 && ver.patch < 5) return Integer.MAX_VALUE + + return MaxDamageCheckerUtil.getMaxDamage(damageable) + } + /** * Set this [ItemStack]s durability from a combination of the * [first] and [second] item's durability values @@ -54,7 +64,9 @@ object ItemUtil { val secondDurability = durability - secondDamage val combinedDurability = firstDurability + secondDurability val newDurability = min(combinedDurability, durability) - it.damage = durability - newDurability + + val maxDamage = maxDamage(it) + it.damage = min(durability - newDurability, maxDamage) itemMeta = it return true } @@ -90,5 +102,5 @@ object ItemUtil { */ fun ItemStack.canMergeWith( other: ItemStack? - ) = (other != null) && (type == other.type || (other.isEnchantedBook())) + ) = (other != null) && (customType == other.customType || (other.isEnchantedBook())) } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilCost.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilCost.kt new file mode 100644 index 0000000..710687c --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilCost.kt @@ -0,0 +1,72 @@ +package xyz.alexcrea.cuanvil.anvil + +import io.delilaheve.util.ConfigOptions +import java.math.BigDecimal +import kotlin.math.min +import io.delilaheve.util.ConfigOptions.getMonetaryMultiplier as moneyMultiplier + +open class AnvilCost { + private val isAlone: Boolean + var valid = true // Get set as invalid if cost can be satisfied + var isMonetary = false + + var generic = 0 + var enchantment = 0 + var repair = 0 + var rename = 0 + var lore = 0 + var illegalPenalty = 0 + var workPenalty = 0 + var recipe = 0 + + constructor(generic: Int) { + this.generic = generic + isAlone = true + } + + constructor() { + isAlone = false + } + + fun asXpCost(): Int { + return generic + enchantment + repair + rename + lore + illegalPenalty + workPenalty + recipe + } + + fun filteredXpCost(ignoreRules: Boolean = false): Int { + val original = asXpCost() + + // Test repair cost limit + return if ( + !ignoreRules && + !ConfigOptions.doRemoveCostLimit && + ConfigOptions.doCapCost + ) { + min(original, ConfigOptions.maxAnvilCost) + } else { + original + } + } + + open fun asMonetaryCost(): BigDecimal { + // multiply by per use type multipliers + return BigDecimal(generic) + .add(BigDecimal(enchantment).multiply(moneyMultiplier("enchantment"))) + .add(BigDecimal(repair).multiply(moneyMultiplier("repair"))) + .add(BigDecimal(rename).multiply(moneyMultiplier("rename"))) + .add(BigDecimal(lore).multiply(moneyMultiplier("lore_edit"))) + .add(BigDecimal(enchantment).multiply(moneyMultiplier("enchantment"))) + .add(BigDecimal(illegalPenalty).multiply(moneyMultiplier("work_penalty"))) + .add(BigDecimal(workPenalty).multiply(moneyMultiplier("work_penalty"))) + .add(BigDecimal(recipe).multiply(moneyMultiplier("recipe"))) + .multiply(moneyMultiplier("global")) + } +} + +class CustomCraftCost(val rawCost: Int): AnvilCost() { + + override fun asMonetaryCost(): BigDecimal { + return BigDecimal(rawCost) + .multiply(moneyMultiplier("global")) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilMergeLogic.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilMergeLogic.kt new file mode 100644 index 0000000..6b106fc --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilMergeLogic.kt @@ -0,0 +1,331 @@ +package xyz.alexcrea.cuanvil.anvil + +import io.delilaheve.CustomAnvil +import io.delilaheve.util.ConfigOptions +import io.delilaheve.util.EnchantmentUtil.combineWith +import io.delilaheve.util.ItemUtil.findEnchantments +import io.delilaheve.util.ItemUtil.isEnchantedBook +import io.delilaheve.util.ItemUtil.repairFrom +import io.delilaheve.util.ItemUtil.setEnchantmentsUnsafe +import io.delilaheve.util.ItemUtil.unitRepair +import org.bukkit.ChatColor +import org.bukkit.Material +import org.bukkit.entity.HumanEntity +import org.bukkit.entity.Player +import org.bukkit.inventory.AnvilInventory +import org.bukkit.inventory.InventoryView +import org.bukkit.inventory.ItemStack +import org.bukkit.inventory.meta.ItemMeta +import org.bukkit.persistence.PersistentDataType +import xyz.alexcrea.cuanvil.dependency.DependencyManager +import xyz.alexcrea.cuanvil.dialog.AnvilRenameDialog +import xyz.alexcrea.cuanvil.enchant.CAEnchantment +import xyz.alexcrea.cuanvil.recipe.AnvilCustomRecipe +import xyz.alexcrea.cuanvil.util.CasedStringUtil +import xyz.alexcrea.cuanvil.util.CustomRecipeUtil +import xyz.alexcrea.cuanvil.util.MaterialUtil.isAir +import xyz.alexcrea.cuanvil.util.MiniMessageUtil +import xyz.alexcrea.cuanvil.util.UnitRepairUtil.getRepair +import xyz.alexcrea.cuanvil.util.anvil.AnvilColorUtil +import xyz.alexcrea.cuanvil.util.anvil.AnvilLoreEditUtil +import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil +import xyz.alexcrea.cuanvil.util.config.LoreEditType +import xyz.alexcrea.cuanvil.util.dialog.AnvilRenameDialogUtil + +object AnvilMergeLogic { + + open class AnvilResult { + companion object { + val EMPTY = AnvilResult(null, AnvilCost()) + } + + val item: ItemStack? + val cost: AnvilCost + val ignoreXpRules: Boolean + + constructor(item: ItemStack?, cost: AnvilCost, ignoreXpRules: Boolean = false) { + this.item = item + this.cost = cost + this.ignoreXpRules = ignoreXpRules + } + + fun isEmpty(): Boolean { + return item == null + } + } + + class UnitRepairResult : AnvilResult { + companion object { + val EMPTY = UnitRepairResult(null, AnvilCost(), 0) + } + + val repairAmount: Int + + constructor(item: ItemStack?, cost: AnvilCost, repairAmount: Int) : super(item, cost) { + this.repairAmount = repairAmount + } + } + + class CustomCraftResult : AnvilResult { + companion object { + val EMPTY = CustomCraftResult(null, CustomCraftCost(0), 0, null) + } + + val customCraftCost: CustomCraftCost + val amount: Int + val recipe: AnvilCustomRecipe? + + constructor( + item: ItemStack?, cost: CustomCraftCost, + amount: Int, recipe: AnvilCustomRecipe? + ) : super(item, cost, true) { + this.customCraftCost = cost + this.amount = amount + this.recipe = recipe + } + } + + class LoreEditResult : AnvilResult { + companion object { + val EMPTY = LoreEditResult(null, AnvilCost(), LoreEditType.APPEND_PAPER) + } + + val type: LoreEditType + + constructor(item: ItemStack?, cost: AnvilCost, type: LoreEditType) : super(item, cost) { + this.type = type + } + } + + fun doRenaming( + view: InventoryView, //TODO use anvil view + inventory: AnvilInventory, + player: Player, first: ItemStack + ): AnvilResult { + val resultItem = DependencyManager.cloneItem(player, first) + val cost = AnvilCost() + cost.rename = handleRename(resultItem, inventory, player) + + // Test/stop if nothing changed. + if (first == resultItem) { + CustomAnvil.log("no right item, But input is same as output") + return AnvilResult.EMPTY + } + + cost.workPenalty = AnvilXpUtil.calculatePenalty(first, null, resultItem, AnvilUseType.RENAME_ONLY) + val result = + DependencyManager.tryTreatAnvilResult(view, inventory, player, resultItem, AnvilUseType.RENAME_ONLY, cost) + + return AnvilResult(result, cost) + } + + private fun processDialogPCD(meta: ItemMeta, player: HumanEntity) { + val text = AnvilRenameDialogUtil.anvilRenameDialog.currentText(player) + return processPCD(meta, player, text) + } + + fun processPCD(meta: ItemMeta, player: HumanEntity, text: String?) { + val keepDialog = ConfigOptions.canUseDialogRename(player) && ConfigOptions.shouldKeepRenameText + + val pdc = meta.persistentDataContainer + if (!keepDialog) + pdc.remove(AnvilRenameDialog.PCD_KEEP_RENAME_TEXT_KEY) + else { + if (text == null || text.isBlank()) + pdc.remove(AnvilRenameDialog.PCD_KEEP_RENAME_TEXT_KEY) + else pdc.set(AnvilRenameDialog.PCD_KEEP_RENAME_TEXT_KEY, PersistentDataType.STRING, text) + } + } + + private fun handleRename(resultItem: ItemStack, inventory: AnvilInventory, player: HumanEntity): Int { + // Can be null + var renameText = ChatColor.stripColor(inventory.renameText) + + var sumCost = 0 + var useColor = false + if (ConfigOptions.renameColorPossible && renameText != null) { + val component = AnvilColorUtil.handleColor( + renameText, + AnvilColorUtil.renamePermission(player) + ) + + if (component != null) { + renameText = MiniMessageUtil.legacy_mm.serialize(component) + + sumCost += ConfigOptions.useOfColorCost + useColor = true + } + } + + // Rename item and add renaming cost + resultItem.itemMeta?.let { + val hasDisplayName = it.hasDisplayName() + val displayName = if (!hasDisplayName) null + else if (useColor) it.displayName + else ChatColor.stripColor(it.displayName) + + + if (!displayName.contentEquals(renameText) && !(displayName == null && + renameText == "" || + //TODO on recent paper check effective name instead + renameText == CasedStringUtil.snakeToUpperSpacedCase(resultItem.type.name.lowercase()) + ) + ) { + it.setDisplayName(renameText) + processDialogPCD(it, player) + resultItem.itemMeta = it + + sumCost += ConfigOptions.itemRenameCost + } + + return sumCost + } + return 0 + } + + fun doMerge( + view: InventoryView, //TODO use anvil view instead + inventory: AnvilInventory, + player: Player, + first: ItemStack, second: ItemStack + ): AnvilResult { + val newEnchants = first.findEnchantments() + .combineWith(second.findEnchantments(), first, player) + var hasChanged = !isIdentical(first.findEnchantments(), newEnchants) + + val resultItem = DependencyManager.cloneItem(player, first) + val cost = AnvilCost() + if (hasChanged) { + resultItem.setEnchantmentsUnsafe(newEnchants) + // Calculate enchantment cost + AnvilXpUtil.getRightValues(second, resultItem, cost) + } + + // Calculate repair cost + if (!first.isEnchantedBook() && !second.isEnchantedBook()) { + // we only need to be concerned with repair when neither item is a book + val repaired = resultItem.repairFrom(first, second) + cost.repair = if (repaired) ConfigOptions.itemRepairCost else 0 + hasChanged = hasChanged || repaired + } + + // Test/stop if nothing changed. + if (!hasChanged) { + CustomAnvil.log("Mergeable with second, But input is same as output") + return AnvilResult.EMPTY + } + // As calculatePenalty edit result, we need to calculate penalty after checking equality + cost.workPenalty = AnvilXpUtil.calculatePenalty(first, second, resultItem, AnvilUseType.MERGE) + // Calculate rename cost + cost.rename = handleRename(resultItem, inventory, player) + + val result = + DependencyManager.tryTreatAnvilResult(view, inventory, player, resultItem, AnvilUseType.MERGE, cost) + + return AnvilResult(result, cost) + } + + private fun isIdentical( + firstEnchants: MutableMap, + resultEnchants: MutableMap + ): Boolean { + if (firstEnchants.size != resultEnchants.size) return false + for (entry in resultEnchants) { + if (firstEnchants.getOrDefault(entry.key, entry.value - 1) != entry.value) return false + } + + return true + } + + // return true if a custom recipe exist with these ingredients + fun testCustomRecipe( + view: InventoryView, //TODO use anvil view instead + inventory: AnvilInventory, + player: Player, + first: ItemStack, second: ItemStack? + ): CustomCraftResult { + val recipe = CustomRecipeUtil.getCustomRecipe(first, second) + CustomAnvil.verboseLog("custom recipe not null? ${recipe != null}") + if (recipe == null) return CustomCraftResult.EMPTY + + val amount = CustomRecipeUtil.getCustomRecipeAmount(recipe, first, second) + + val resultItem: ItemStack = DependencyManager.cloneItem(player, recipe.resultItem!!) + resultItem.amount *= amount + + // Maybe add an option on custom craft to ignore/not ignore penalty ?? + val xpCost = recipe.determineCost(amount, first, resultItem) + + val cost = CustomCraftCost(xpCost) + // This is for displayed cost + cost.recipe = if (recipe.removeExactLinearXp) AnvilXpUtil.calculateMinimumLevelForXp(xpCost) + else AnvilXpUtil.calculateLevelForXp(xpCost) + + val result = + DependencyManager.tryTreatAnvilResult(view, inventory, player, resultItem, AnvilUseType.CUSTOM_CRAFT, cost) + return CustomCraftResult(result, cost, amount, recipe) + } + + fun testUnitRepair( + view: InventoryView, //TODO use anvil view + inventory: AnvilInventory, + player: Player, + first: ItemStack, second: ItemStack + ): UnitRepairResult { + val unitRepairAmount = first.getRepair(second) ?: return UnitRepairResult.EMPTY + + return testUnitRepair(view, inventory, player, first, second, unitRepairAmount) + } + + fun testUnitRepair( + view: InventoryView, //TODO use anvil view instead + inventory: AnvilInventory, + player: Player, + first: ItemStack, second: ItemStack, + unitRepairAmount: Double + ): UnitRepairResult { + val resultItem = DependencyManager.cloneItem(player, first) + val cost = AnvilCost() + cost.rename = handleRename(resultItem, inventory, player) + + val repairAmount = resultItem.unitRepair(second.amount, unitRepairAmount) + if (repairAmount > 0) + cost.repair = repairAmount * ConfigOptions.unitRepairCost + + // We do not care about right item penalty for unit repair + cost.workPenalty = AnvilXpUtil.calculatePenalty(first, null, resultItem, AnvilUseType.UNIT_REPAIR) + + // Test/stop if nothing changed. + if (first == resultItem) { + CustomAnvil.log("unit repair, But input is same as output") + return UnitRepairResult.EMPTY + } + + val result = + DependencyManager.tryTreatAnvilResult(view, inventory, player, resultItem, AnvilUseType.UNIT_REPAIR, cost) + return UnitRepairResult(result, cost, repairAmount) + } + + fun testLoreEdit( + player: Player, + first: ItemStack, second: ItemStack + ): LoreEditResult { + val type = second.type + + val result = if (Material.WRITABLE_BOOK == type) + AnvilLoreEditUtil.tryLoreEditByBook(player, first, second) + else if (Material.PAPER == type) + AnvilLoreEditUtil.tryLoreEditByPaper(player, first, second) + else LoreEditResult.EMPTY + + if (result.isEmpty()) return result + + if (result.item!!.isAir || first == result.item) { + CustomAnvil.log("lore edit, But input is same as output") + return LoreEditResult.EMPTY + } + + return result + } + +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilUseType.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilUseType.kt similarity index 66% rename from src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilUseType.kt rename to src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilUseType.kt index d17e908..67782f3 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilUseType.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilUseType.kt @@ -1,60 +1,60 @@ -package xyz.alexcrea.cuanvil.util +package xyz.alexcrea.cuanvil.anvil import org.bukkit.Material -import xyz.alexcrea.cuanvil.config.WorkPenaltyType.WorkPenaltyPart -import xyz.alexcrea.cuanvil.util.config.LoreEditType +import xyz.alexcrea.cuanvil.config.WorkPenaltyType +import xyz.alexcrea.cuanvil.util.anvil.AnvilUseTypeUtil enum class AnvilUseType( val typeName: String, val path: String, - val defaultPenalty: WorkPenaltyPart, + val defaultPenalty: WorkPenaltyType.WorkPenaltyPart, val displayName: String, val displayMat: Material ) { RENAME_ONLY( "rename_only", - WorkPenaltyPart(false, true), + WorkPenaltyType.WorkPenaltyPart(false, true), "Rename Only", Material.NAME_TAG ), MERGE( "merge", - WorkPenaltyPart(true, true), + WorkPenaltyType.WorkPenaltyPart(true, true), "Merge", Material.ANVIL ), UNIT_REPAIR( "unit_repair", - WorkPenaltyPart(true, true), + WorkPenaltyType.WorkPenaltyPart(true, true), "Unit Repair", Material.DIAMOND ), CUSTOM_CRAFT( "custom_craft", - WorkPenaltyPart(false, false), + WorkPenaltyType.WorkPenaltyPart(false, false), "Custom Craft", Material.CRAFTING_TABLE ), LORE_EDIT_BOOK_APPEND( "lore_edit_book_append", "lore_edit.book_and_quil.append", - WorkPenaltyPart(false, false), + WorkPenaltyType.WorkPenaltyPart(false, false), "Book Add", Material.WRITABLE_BOOK ), LORE_EDIT_BOOK_REMOVE( "lore_edit_book_remove", "lore_edit.book_and_quil.remove", - WorkPenaltyPart(false, false), + WorkPenaltyType.WorkPenaltyPart(false, false), "Book Remove", Material.WRITABLE_BOOK ), LORE_EDIT_PAPER_APPEND( "lore_edit_paper_append", "lore_edit.paper.append_line", - WorkPenaltyPart(false, false), + WorkPenaltyType.WorkPenaltyPart(false, false), "Paper Add", Material.WRITABLE_BOOK ), LORE_EDIT_PAPER_REMOVE( "lore_edit_paper_remove", "lore_edit.paper.remove_line", - WorkPenaltyPart(false, false), + WorkPenaltyType.WorkPenaltyPart(false, false), "Paper Remove", Material.WRITABLE_BOOK ), ; constructor( typeName: String, - defaultPenalty: WorkPenaltyPart, + defaultPenalty: WorkPenaltyType.WorkPenaltyPart, displayName: String, displayMat: Material ) : this( diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/CASubCommand.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/CASubCommand.kt new file mode 100644 index 0000000..4c71c68 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/CASubCommand.kt @@ -0,0 +1,46 @@ +package xyz.alexcrea.cuanvil.command + +import org.bukkit.ChatColor +import org.bukkit.command.Command +import org.bukkit.command.CommandExecutor +import org.bukkit.command.CommandSender + +abstract class CASubCommand: CommandExecutor { + + private var alreadySaid = false; + override fun onCommand( + sender: CommandSender, + cmd: Command, + cmdstr: String, + args: Array + ): Boolean { + if(!alreadySaid){ + sender.sendMessage(ChatColor.RED.toString() + + "Please not that this command will be replaced as a subcommand of `/customanvil` or `/ca`") + alreadySaid = true + } + + return executeCommand(sender, cmd, cmdstr, args) + } + + abstract fun executeCommand( + sender: CommandSender, + cmd: Command, + cmdstr: String, + args: Array): Boolean + + open fun allowed(sender: CommandSender): Boolean { + return true + } + + open fun tabCompleter( + sender: CommandSender, + args: Array, + list: MutableList) { + } + + open fun description(): String { + return "no description" + } + +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCommand.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCommand.kt new file mode 100644 index 0000000..e5e689e --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCommand.kt @@ -0,0 +1,96 @@ +package xyz.alexcrea.cuanvil.command + +import com.google.common.collect.ImmutableMap +import io.delilaheve.CustomAnvil +import org.bukkit.command.Command +import org.bukkit.command.CommandExecutor +import org.bukkit.command.CommandSender +import org.bukkit.command.TabCompleter +import xyz.alexcrea.cuanvil.util.MetricsUtil +import java.util.ArrayList + +class CustomAnvilCommand(plugin: CustomAnvil) : CommandExecutor, TabCompleter { + + // Name of the generic command + companion object { + private const val genericCommandName = "customanvil" + } + + private val editConfigCommand = EditConfigExecutor() + private val helpCommand = HelpExecutor() + private val commands = ImmutableMap.of( + "gui", editConfigCommand, + "reload", ReloadExecutor(), + "diagnostic", DiagnosticExecutor(), + "help", helpCommand, + ) + + init { + val self = plugin.getCommand(genericCommandName)!! + self.setExecutor(this) + self.tabCompleter = this + + helpCommand.commands = commands + } + + override fun onCommand( + sender: CommandSender, + cmd: Command, + cmdstr: String, + args: Array + ): Boolean { + // Find sub command to execute based on the provided command name + val subcmd: CASubCommand? + val newargs: Array + if(args.isEmpty()) { + subcmd = editConfigCommand + newargs = args + }else { + subcmd = commands[args[0].lowercase()] + newargs = args.copyOfRange(1, args.size) + } + + if(subcmd == null || !subcmd.allowed(sender)) { + sender.sendMessage("Invalid subcommand. run `$cmdstr help` to see available commands") + return true + } + + try { + return subcmd.executeCommand(sender, cmd, cmdstr, newargs) + } catch (e: Throwable) { + MetricsUtil.trackError(e) + sender.sendMessage("§cError running this command") + return false + } + } + + override fun onTabComplete( + sender: CommandSender, + cmd: Command, + cmdstr: String, + args: Array + ): MutableList { + val result = ArrayList() + if(args.size < 2) { + for ((key, cmd) in commands) { + if(!cmd.allowed(sender)) continue + result.add(key) + } + } else { + val subcmd = commands[args[0].lowercase()] + + if(subcmd != null) { + val newArgs = args.copyOfRange(1, args.size) + if(!subcmd.allowed(sender)) return result + + subcmd.tabCompleter(sender, newArgs, result) + } + } + + //assumed all provided tab completed string are lowercase + return result.stream() + .filter { it.startsWith(args[args.size - 1]) } + .sorted() + .toList() + } +} diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt new file mode 100644 index 0000000..053a3fc --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt @@ -0,0 +1,347 @@ +package xyz.alexcrea.cuanvil.command + +import com.github.stefvanschie.inventoryframework.inventoryview.interface_.InventoryViewUtil +import io.delilaheve.CustomAnvil +import net.md_5.bungee.api.chat.ClickEvent +import net.md_5.bungee.api.chat.HoverEvent +import net.md_5.bungee.api.chat.TextComponent +import net.md_5.bungee.api.chat.hover.content.Text +import org.bukkit.Bukkit +import org.bukkit.ChatColor +import org.bukkit.Material +import org.bukkit.command.Command +import org.bukkit.command.CommandSender +import org.bukkit.enchantments.Enchantment +import org.bukkit.entity.HumanEntity +import org.bukkit.entity.Player +import org.bukkit.event.inventory.InventoryType +import org.bukkit.event.inventory.PrepareAnvilEvent +import org.bukkit.inventory.AnvilInventory +import org.bukkit.inventory.InventoryView +import org.bukkit.inventory.ItemStack +import org.bukkit.inventory.meta.Damageable +import org.bukkit.inventory.meta.EnchantmentStorageMeta +import org.bukkit.plugin.Plugin +import org.bukkit.plugin.RegisteredListener +import xyz.alexcrea.cuanvil.dependency.DependencyManager +import xyz.alexcrea.cuanvil.dependency.packet.NoPacketManager +import xyz.alexcrea.cuanvil.dependency.packet.ProtocoLibWrapper +import xyz.alexcrea.cuanvil.enchant.CAEnchantmentRegistry +import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener +import xyz.alexcrea.cuanvil.util.MetricsUtil +import java.util.* +import java.util.stream.Collectors + + +class DiagnosticExecutor: CASubCommand() { + + companion object{ + private const val NO_DIAG_PERM = "You do not have permission to diagnostic this server" + + fun fetchNMSType(): String { + val packetManager = DependencyManager.packetManager + val packetManagerClass = packetManager.javaClass + + val className = packetManagerClass.name + val result = if(className.contains("PaperPacket")) { + "Paper" + } else { + when (packetManagerClass) { + ProtocoLibWrapper::class.java -> "Protocolib" + NoPacketManager::class.java -> "None" + else -> "Version Specific" + } + } + + + return "$result ${if(packetManager.canSetInstantBuild) '✅' else '❌'}" + } + } + + enum class DiagParams(val value: String) { + OS_PRIVACY("os_privacy"), + PLUGIN_PRIVACY("plugin_privacy"), + NO_MERGE_TEST("no_merge_test"), + FULL_ENCHANTMENT_DATA("full_enchantment_data"), + INCLUDE_LAST_ERROR("include_last_error"), + } + + private fun fetchParameters(args: Array): EnumSet { + val result = EnumSet.noneOf(DiagParams::class.java) + val argSet = HashSet() + + for (string in args) { + argSet.add(string.lowercase()) + } + + for (param in DiagParams.entries) { + if(argSet.contains(param.value)) + result.add(param) + } + + return result + } + + override fun tabCompleter( + sender: CommandSender, + args: Array, + list: MutableList) { + if(!allowed(sender)) return + + val map = fetchParameters(args) + for (param in DiagParams.entries) { + if(!map.contains(param)) + list.add(param.value) + } + + } + + override fun executeCommand( + sender: CommandSender, + cmd: Command, + cmdstr: String, + args: Array + ): Boolean { + if (!allowed(sender)) { + sender.sendMessage(NO_DIAG_PERM) + return false + } + + val stb = StringBuilder("```\n") + val params = fetchParameters(args) + var hasError = false + try { + diagnostic(sender, stb, params) + } catch(e: Throwable){ + stb.append("\n\nError happened trying to get diagnostic data:\n") + .append(e.message).append("\n") + .append(e.stackTrace.joinToString("\n")) + hasError = true + e.printStackTrace() + } + + stb.append("\n```") + + if (sender is HumanEntity) { + if(hasError) + sender.spigot().sendMessage(TextComponent(ChatColor.RED.toString() + "There was an error running the diagnostic")) + val message = TextComponent(ChatColor.GREEN.toString() + "Click to copy diagnostic data") + + message.clickEvent = ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, stb.toString()) + message.hoverEvent = HoverEvent(HoverEvent.Action.SHOW_TEXT, Text("§7Click to copy")) + + sender.spigot().sendMessage(message); + } else { + sender.sendMessage(stb.toString()); + } + + return true + } + + override fun allowed(sender: CommandSender): Boolean { + return sender.hasPermission(CustomAnvil.diagnosticPermission) + } + + fun diagnostic(sender: CommandSender, stb: StringBuilder, params: Set){ + stb.append("Server Info\n") + val version = CustomAnvil.instance.description.version + stb.append("\nPlugin Version: ").append(version) + if(version.contains("dev")) stb.append(" (alpha)") + + stb.append("\nLatest Update: ").append(CustomAnvil.latestVer) + stb.append("\nServer Version: ").append(Bukkit.getVersion()).append(" (").append(Bukkit.getName()).append(')') + stb.append("\nPlugin Enabled: ").append(if(CustomAnvil.instance.isEnabled) "Yes" else "No") + stb.append("\nNMS type: ").append(fetchNMSType()) + if(!params.contains(DiagParams.OS_PRIVACY)) { + stb.append("\nJava Version: ").append(System.getProperty("java.version")) + stb.append("\nOS: ").append(System.getProperty("os.name")).append(" ") + .append(System.getProperty("os.version")) + .append(System.getProperty("os.arch")) + } + + stb.append("\nHad detect error: ").append(if(MetricsUtil.lastError != null) "Yes" else "No") + + if(!params.contains(DiagParams.PLUGIN_PRIVACY)) { + pluginListDiag(sender, stb) + } + prepareAnvilListeners(stb) + + if(!params.contains(DiagParams.NO_MERGE_TEST)){ + if(sender is Player) testMerge(sender, stb) + } + + stb.append("\n\nEnchantments data:") + partialEnchantmentData(stb) + if(params.contains(DiagParams.FULL_ENCHANTMENT_DATA)){ + fullEnchantmentData(stb) + } + + if(params.contains(DiagParams.INCLUDE_LAST_ERROR)){ + includeLastError(stb) + } + } + + private fun testMerge(player: Player, stb: StringBuilder) { + val sword = ItemStack(Material.DIAMOND_SWORD) + val damagedSword = sword.clone() + val enchantedSword = sword.clone() + val enchantedBook = ItemStack(Material.ENCHANTED_BOOK) + val unitForRepair = ItemStack(Material.DIAMOND) + + var meta = damagedSword.itemMeta + (meta as Damageable).damage = 5 + damagedSword.itemMeta = meta + + meta = enchantedSword.itemMeta + meta!!.addEnchant(Enchantment.DAMAGE_ALL, 1, true) + enchantedSword.itemMeta = meta + + meta = enchantedBook.itemMeta + (meta as EnchantmentStorageMeta).addStoredEnchant(Enchantment.DAMAGE_ALL, 1, true) + enchantedBook.itemMeta = meta + + stb.append("\n\nItem to Item repair:") + simulateAnvil(player, stb, damagedSword, damagedSword, sword) + + stb.append("\n\nUnit repair:") + simulateAnvil(player, stb, damagedSword, unitForRepair, sword) + + stb.append("\n\nEnchanting an item:") + simulateAnvil(player, stb, sword, enchantedBook, enchantedSword) + } + + private val Plugin.pluginNameDisplay: String + get() { + return this.name + " v" + this.description.version + } + + override fun description(): String { + return "Basic diagnostic of this plugin" + } + + private fun pluginListDiag(sender: CommandSender, stb: StringBuilder) { + val enabledPlugins: MutableList = ArrayList() + val disabledPlugins: MutableList = ArrayList() + for (plugin in Bukkit.getPluginManager().plugins) { + if (plugin.isEnabled) { + enabledPlugins.add(plugin) + } else { + disabledPlugins.add(plugin) + } + } + + stb.append("\nEnabled Plugins: ").append( + enabledPlugins.stream() + .map { plugin -> plugin!!.pluginNameDisplay } + .reduce { a: String?, b: String? -> "$a, $b" }.orElse("None") + ) + + stb.append("\nDisabled Plugins: ").append( + disabledPlugins.stream() + .map { plugin -> plugin!!.pluginNameDisplay } + .reduce { a: String?, b: String? -> "$a, $b" }.orElse("None") + ) + } + + fun prepareAnvilListeners(stb: StringBuilder) { + val eventListeners: MutableSet = Arrays + .stream( + PrepareAnvilEvent + .getHandlerList() + .getRegisteredListeners() + ) + .map { obj: RegisteredListener? -> obj!!.plugin } + .collect(Collectors.toSet()) + + eventListeners.remove(CustomAnvil.instance) + stb.append("\nPrepare Anvil Listeners: ").append( + if (eventListeners.isEmpty()) "None" else eventListeners.stream() + .map { plugin -> plugin!!.pluginNameDisplay } + .reduce { a: String?, b: String? -> "$a, $b" }.orElse("None") + ) + } + + fun simulateAnvil(player: Player, stb: StringBuilder, left: ItemStack?, right: ItemStack?, result: ItemStack?) { + var invView: InventoryView + var event: PrepareAnvilEvent + try { + val fakeInv = Bukkit.createInventory(player, InventoryType.ANVIL) + invView = player.openInventory(fakeInv)!! + event = PrepareAnvilEvent(invView, result) + } catch (e: Throwable) { + // Help + val menuTypeClazz = Class.forName("org.bukkit.inventory.MenuType") + val anvilTypeField = menuTypeClazz.getField("ANVIL") + val anvilType = anvilTypeField.get(null) + val createMethod = anvilType.javaClass.getMethod("create", HumanEntity::class.java) + invView = createMethod.invoke(anvilType, player) as InventoryView + + player.openInventory(invView) + + val anvilViewClass = Class.forName("org.bukkit.inventory.view.AnvilView") + val constructor = PrepareAnvilEvent::class.java.getConstructor(anvilViewClass, ItemStack::class.java) + event = constructor.newInstance(invView, result) + } + + val fakeInv = InventoryViewUtil.getInstance().getTopInventory(invView) as AnvilInventory + fakeInv.setItem(0, left) + fakeInv.setItem(1, right) + + val xp = fakeInv.repairCost + val maxXp = fakeInv.maximumRepairCost + val mergeResult = fakeInv.getItem(2) + stb.append("\n${if(result == mergeResult) "E" else "Une"}xpected Result") + + PrepareAnvilListener().anvilCombineCheck(event) + // Now we check if item and xp same + stb.append("\nXP/Max XP: ") + .append(if(fakeInv.repairCost == xp) "Correct" else "Incorrect") + .append("/") + .append(if(fakeInv.maximumRepairCost == maxXp) "Correct" else "Incorrect") + .append(" (${fakeInv.repairCost} $xp|${fakeInv.maximumRepairCost} $maxXp)") + .append("\nMerge result: ") + .append(if(fakeInv.getItem(2) == mergeResult) "Correct" else "Incorrect") + + PrepareAnvilListener.IS_EMPTY_TEST = true + Bukkit.getPluginManager().callEvent(event) + stb.append("\nNull result test: ") + .append(if(event.result == null) "Correct" else "Incorrect") + + fakeInv.setItem(0, null) + fakeInv.setItem(1, null) + fakeInv.setItem(2, null) + player.closeInventory() + } + + private fun fullEnchantmentData(stb: StringBuilder) { + for (enchantment in CAEnchantmentRegistry.getInstance().values()) { + stb.append("\n- ").append(enchantment.key.toString()) + .append(" ").append(enchantment.name) + .append(" ").append(enchantment.defaultMaxLevel()) + } + } + + private fun partialEnchantmentData(stb: StringBuilder) { + val map = HashMap() + for (enchant in CAEnchantmentRegistry.getInstance().values()) { + map[enchant.key.namespace] = map.getOrDefault(enchant.key.namespace, 0) + 1 + } + + stb.append("\nNamespaces: ${ + map.entries.stream() + .map { (key, value) -> "$key ($value)" } + .reduce { a, b -> "$a, $b" }.get() + }") + + } + + private fun includeLastError(stb: StringBuilder) { + val e = MetricsUtil.lastError ?: return + + stb.append("\n\nLast stack trace: ${e.stackTraceToString()}") + + + + } + +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/EditConfigExecutor.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/EditConfigExecutor.kt index 85761d9..fba89b7 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/command/EditConfigExecutor.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/EditConfigExecutor.kt @@ -2,21 +2,25 @@ package xyz.alexcrea.cuanvil.command import io.delilaheve.CustomAnvil import org.bukkit.command.Command -import org.bukkit.command.CommandExecutor import org.bukkit.command.CommandSender import org.bukkit.entity.HumanEntity -import xyz.alexcrea.cuanvil.dependency.DependencyManager +import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil import xyz.alexcrea.cuanvil.gui.config.MainConfigGui import xyz.alexcrea.cuanvil.gui.util.GuiGlobalActions -class EditConfigExecutor : CommandExecutor { +class EditConfigExecutor: CASubCommand() { - override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array): Boolean { - if (!sender.hasPermission(CustomAnvil.editConfigPermission)) { + override fun executeCommand(sender: CommandSender, + cmd: Command, + cmdstr: String, + args: Array): Boolean { + if (sender !is HumanEntity) return false + + if (!allowed(sender)) { sender.sendMessage(GuiGlobalActions.NO_EDIT_PERM) return false } - if(DependencyManager.isFolia){ + if(PlatformUtil.isFolia){ sender.sendMessage("§cIt look like you are using Folia. Sadly Custom Anvil do not support Config gui for Folia.") sender.sendMessage("§eIt is may come in a future version.") sender.sendMessage("") @@ -25,10 +29,17 @@ class EditConfigExecutor : CommandExecutor { return false } - if (sender !is HumanEntity) return false MainConfigGui.getInstance().show(sender) return true } + override fun allowed(sender: CommandSender): Boolean { + return sender.hasPermission(CustomAnvil.editConfigPermission) + } + + override fun description(): String { + return "Gui to edit the plugin's config" + } + } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/HelpExecutor.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/HelpExecutor.kt new file mode 100644 index 0000000..697f1ee --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/HelpExecutor.kt @@ -0,0 +1,32 @@ +package xyz.alexcrea.cuanvil.command + +import com.google.common.collect.ImmutableMap +import org.bukkit.command.Command +import org.bukkit.command.CommandSender + +class HelpExecutor: CASubCommand() { + + lateinit var commands: ImmutableMap + + override fun executeCommand(sender: CommandSender, + cmd: Command, + cmdstr: String, + args: Array): Boolean { + + val stb = StringBuilder("List of available commands:") + for ((key, cmd) in commands) { + if(!cmd.allowed(sender)) continue + + stb.append("\n- $key: ").append(cmd.description()) + } + + sender.sendMessage(stb.toString()) + + return true + } + + override fun description(): String { + return "Help command" + } + +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/ReloadExecutor.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/ReloadExecutor.kt index 38a98c0..ef23e7d 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/command/ReloadExecutor.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/ReloadExecutor.kt @@ -9,11 +9,15 @@ import xyz.alexcrea.cuanvil.api.event.CAConfigReadyEvent import xyz.alexcrea.cuanvil.config.ConfigHolder import xyz.alexcrea.cuanvil.dependency.DependencyManager import xyz.alexcrea.cuanvil.gui.config.global.* -import xyz.alexcrea.cuanvil.update.Update_1_21 +import xyz.alexcrea.cuanvil.update.UpdateHandler -class ReloadExecutor : CommandExecutor { - override fun onCommand(sender: CommandSender, cmd: Command, cmdstr: String, args: Array): Boolean { - if (!sender.hasPermission(CustomAnvil.commandReloadPermission)) { +class ReloadExecutor : CASubCommand() { + + override fun executeCommand(sender: CommandSender, + cmd: Command, + cmdstr: String, + args: Array): Boolean { + if (!allowed(sender)) { sender.sendMessage("§cYou do not have permission to reload the config") return false } @@ -31,6 +35,14 @@ class ReloadExecutor : CommandExecutor { return commandSuccess } + override fun allowed(sender: CommandSender): Boolean { + return sender.hasPermission(CustomAnvil.commandReloadPermission) + } + + override fun description(): String { + return "Reload the configuration of this plugin" + } + /** * Execute the command, return true if success or false otherwise */ @@ -48,8 +60,8 @@ class ReloadExecutor : CommandExecutor { UnitRepairConfigGui.getCurrentInstance()?.reloadValues() CustomRecipeConfigGui.getCurrentInstance()?.reloadValues() - // temporary: handle 1.21 update - Update_1_21.handleUpdate() + // handle minecraft version update + UpdateHandler.handleMCVersionUpdate() // Handle dependency reload DependencyManager.handleConfigReload() diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt index b962b03..b730a0e 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt @@ -1,30 +1,45 @@ package xyz.alexcrea.cuanvil.dependency +import com.maddoxh.superEnchants.SuperEnchants import io.delilaheve.CustomAnvil +import net.kyori.adventure.text.Component import org.bukkit.Bukkit import org.bukkit.ChatColor +import org.bukkit.command.CommandSender import org.bukkit.entity.HumanEntity +import org.bukkit.entity.Player import org.bukkit.event.inventory.InventoryClickEvent import org.bukkit.event.inventory.PrepareAnvilEvent import org.bukkit.inventory.AnvilInventory +import org.bukkit.inventory.Inventory +import org.bukkit.inventory.InventoryView import org.bukkit.inventory.ItemStack +import xyz.alexcrea.cuanvil.anvil.AnvilCost +import xyz.alexcrea.cuanvil.anvil.AnvilUseType +import xyz.alexcrea.cuanvil.api.event.listener.CAClickResultBypassEvent +import xyz.alexcrea.cuanvil.api.event.listener.CAEarlyPreAnvilBypassEvent +import xyz.alexcrea.cuanvil.api.event.listener.CAPreAnvilBypassEvent +import xyz.alexcrea.cuanvil.api.event.listener.CATreatAnvilResult2Event import xyz.alexcrea.cuanvil.config.ConfigHolder -import xyz.alexcrea.cuanvil.dependency.gui.ExternGuiTester -import xyz.alexcrea.cuanvil.dependency.gui.GuiTesterSelector +import xyz.alexcrea.cuanvil.dependency.datapack.DataPackDependency +import xyz.alexcrea.cuanvil.dependency.gui.GenericExternGuiTester import xyz.alexcrea.cuanvil.dependency.packet.PacketManager import xyz.alexcrea.cuanvil.dependency.packet.PacketManagerSelector +import xyz.alexcrea.cuanvil.dependency.plugins.* import xyz.alexcrea.cuanvil.dependency.scheduler.BukkitScheduler import xyz.alexcrea.cuanvil.dependency.scheduler.FoliaScheduler import xyz.alexcrea.cuanvil.dependency.scheduler.TaskScheduler +import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil +import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil.componentLore import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener.Companion.ANVIL_OUTPUT_SLOT +import xyz.alexcrea.cuanvil.util.MetricsUtil.trackError import java.util.logging.Level object DependencyManager { - var isFolia: Boolean = false lateinit var scheduler: TaskScheduler lateinit var packetManager: PacketManager - var externGuiTester: ExternGuiTester? = null + var externGuiTester: GenericExternGuiTester = GenericExternGuiTester() var enchantmentSquaredCompatibility: EnchantmentSquaredDependency? = null var ecoEnchantCompatibility: EcoEnchantDependency? = null @@ -33,12 +48,17 @@ object DependencyManager { var disenchantmentCompatibility: DisenchantmentDependency? = null var havenBagsCompatibility: HavenBagsDependency? = null + var axPlayerWarpsCompatibility: AxPlayerWarpsDependency? = null + + var itemsAdderCompatibility: ItemsAdderDependency? = null + + val genericDependencies = ArrayList() + fun loadDependency() { val pluginManager = Bukkit.getPluginManager() // Bukkit or Paper scheduler ? - isFolia = testIsFolia() - scheduler = if (isFolia) { + scheduler = if (PlatformUtil.isFolia) { CustomAnvil.instance.logger.info("Folia detected... Custom Anvil Folia support is experimental. issues are more likely to happens.") FoliaScheduler() @@ -47,7 +67,6 @@ object DependencyManager { // Packet Manager val forceProtocolib = ConfigHolder.DEFAULT_CONFIG.config.getBoolean("force_protocolib", false) packetManager = PacketManagerSelector.selectPacketManager(forceProtocolib) - externGuiTester = GuiTesterSelector.selectGuiTester // Enchantment Squared dependency if (pluginManager.isPluginEnabled("EnchantsSquared")) { @@ -79,11 +98,40 @@ object DependencyManager { havenBagsCompatibility!!.redirectListeners() } + // AxPlayerWarps dependency + if (pluginManager.isPluginEnabled("AxPlayerWarps")) { + axPlayerWarpsCompatibility = AxPlayerWarpsDependency() + } + + if (pluginManager.isPluginEnabled("ItemsAdder")){ + val dependency = ItemsAdderDependency(pluginManager.getPlugin("ItemsAdder")!!) + itemsAdderCompatibility = dependency + genericDependencies.add(dependency) + } + + // "Generic" dependencies + if (pluginManager.isPluginEnabled("ToolStats")) + genericDependencies.add(ToolStatsDependency(pluginManager.getPlugin("ToolStats")!!)) + + if (pluginManager.isPluginEnabled("ItemsAdder")) + genericDependencies.add(GenericPluginDependency(pluginManager.getPlugin("ItemsAdder")!!)) + + if (pluginManager.isPluginEnabled("SuperEnchants")){ + val compatibility = SuperEnchantDependency(pluginManager.getPlugin("SuperEnchants")!! as SuperEnchants) + if(compatibility.registerEnchantments()) + genericDependencies.add(compatibility) + } + + for (dependency in genericDependencies) + dependency.redirectListeners() + } fun handleCompatibilityConfig() { enchantmentSquaredCompatibility?.registerPluginConfiguration() + // datapacks + DataPackDependency.handleDatapackConfigs() } fun registerEnchantments() { @@ -101,31 +149,74 @@ object DependencyManager { ecoEnchantCompatibility?.handleConfigReload() } + private fun logException(target: CommandSender, e: Exception) { + CustomAnvil.instance.logger.log( + Level.SEVERE, + "Error while trying to handle custom anvil supported plugin: ", + e + ) + trackError(e) + + // Finally, warn the player + target.sendMessage( + "[" + ChatColor.YELLOW.toString() + "CustomAnvil" + ChatColor.WHITE.toString() + "] " + + ChatColor.RED.toString() + "Error while handling the anvil." + ) + } + + private fun logExceptionAndClear(target: CommandSender, inventory: Inventory, e: Exception) { + // Just in case to avoid illegal items + inventory.setItem(ANVIL_OUTPUT_SLOT, null) + + logException(target, e) + } + // Return true if should bypass (either by a dependency or error) - fun tryEventPreAnvilBypass(event: PrepareAnvilEvent, player: HumanEntity): Boolean { + // called before immutability test + fun earlyTryEventPreAnvilBypass(event: PrepareAnvilEvent, player: HumanEntity): Boolean { try { - return unsafeTryEventPreAnvilBypass(event, player) + return earlyUnsafeTryEventPreAnvilBypass(event, player) } catch (e: Exception) { - CustomAnvil.instance.logger.log( - Level.SEVERE, - "Error while trying to handle custom anvil supported plugin: ", - e - ) - - // Just in case to avoid illegal items - event.inventory.setItem(ANVIL_OUTPUT_SLOT, null) - - // Finally, warn the player, maybe a lot of time but better warn than do nothing - event.view.player.sendMessage(ChatColor.RED.toString() + "Error while handling the anvil.") + logExceptionAndClear(event.view.player, event.inventory, e) return true } } - private fun unsafeTryEventPreAnvilBypass(event: PrepareAnvilEvent, player: HumanEntity): Boolean { - var bypass = false + private fun earlyUnsafeTryEventPreAnvilBypass(event: PrepareAnvilEvent, player: HumanEntity): Boolean { + // Run the event + val bypassEvent = CAEarlyPreAnvilBypassEvent(event) + Bukkit.getPluginManager().callEvent(bypassEvent) + + var bypass = bypassEvent.isCancelled + + // Test if the inventory is a gui(version specific) + if (!bypass && externGuiTester.testIfGui(event.view)) bypass = true + + // Test if in an ax player warp rating gui + if (!bypass && (axPlayerWarpsCompatibility?.testIfGui(player) == true)) bypass = true + + return bypass + } + + // Return true if should bypass (either by a dependency or error) + fun tryEventPreAnvilBypass(event: PrepareAnvilEvent, player: Player): Boolean { + try { + return unsafeTryEventPreAnvilBypass(event, player) + } catch (e: Exception) { + logExceptionAndClear(event.view.player, event.inventory, e) + return true + } + } + + private fun unsafeTryEventPreAnvilBypass(event: PrepareAnvilEvent, player: Player): Boolean { + // Run the event + val bypassEvent = CAPreAnvilBypassEvent(event) + Bukkit.getPluginManager().callEvent(bypassEvent) + + var bypass = bypassEvent.isCancelled // Test if disenchantment used prepare anvil - if (disenchantmentCompatibility?.testPrepareAnvil(event, player) == true) bypass = true + if (!bypass && (disenchantmentCompatibility?.testPrepareAnvil(event, player) == true)) bypass = true // Test heaven bags used prepare anvil if (!bypass && (havenBagsCompatibility?.testPrepareAnvil(event, player) == true)) bypass = true @@ -133,36 +224,36 @@ object DependencyManager { // Test excellent enchantments used prepare anvil if (!bypass && (excellentEnchantsCompatibility?.testPrepareAnvil(event) == true)) bypass = true - // Test if the inventory is a gui(version specific) - if (!bypass && (externGuiTester?.testIfGui(event.view) == true)) bypass = true - + for (genericDependency in genericDependencies) { + if (!bypass && genericDependency.testPrepareAnvil(event)) bypass = true + } return bypass } - // Return true only if error occurred (and so should bypass rest) - fun tryTreatAnvilResult(event: PrepareAnvilEvent, result: ItemStack): Boolean { + // Return null if there was an issue + fun tryTreatAnvilResult( + view: InventoryView, + inventory: Inventory, // TODO REMOVE, use view instead on legacy removal + player: HumanEntity, + result: ItemStack, + useType: AnvilUseType, + cost: AnvilCost + ): ItemStack? { + val treatEvent = CATreatAnvilResult2Event(view, inventory, useType, result, cost) try { - unsafeTryTreatAnvilResult(event, result) - return false + unsafeTryTreatAnvilResult(treatEvent) + return treatEvent.result } catch (e: Exception) { - CustomAnvil.instance.logger.log( - Level.SEVERE, - "Error while trying to handle custom anvil supported plugin: ", - e - ) - - // Just in case to avoid illegal items - event.inventory.setItem(ANVIL_OUTPUT_SLOT, null) - - // Finally, warn the player, maybe a lot of time but better warn than do nothing - event.view.player.sendMessage(ChatColor.RED.toString() + "Error while handling the anvil.") - return true + logExceptionAndClear(player, inventory, e) + return null } } - private fun unsafeTryTreatAnvilResult(event: PrepareAnvilEvent, result: ItemStack) { - excellentEnchantsCompatibility?.treatAnvilResult(event, result) + private fun unsafeTryTreatAnvilResult(event: CATreatAnvilResult2Event) { + Bukkit.getPluginManager().callEvent(event) + + excellentEnchantsCompatibility?.treatAnvilResult(event) } // Return true if should bypass (either by a dependency or error) @@ -170,26 +261,20 @@ object DependencyManager { try { return unsafeTryClickAnvilResultBypass(event, inventory) } catch (e: Exception) { - CustomAnvil.instance.logger.log( - Level.SEVERE, - "Error while trying to handle custom anvil supported plugin: ", - e - ) - - // Just in case to avoid illegal items - event.inventory.setItem(ANVIL_OUTPUT_SLOT, null) - - // Finally, warn the player, maybe a lot of time but better warn than do nothing - event.whoClicked.sendMessage(ChatColor.RED.toString() + "Error while handling the anvil.") + logExceptionAndClear(event.view.player, event.inventory, e) return true } } private fun unsafeTryClickAnvilResultBypass(event: InventoryClickEvent, inventory: AnvilInventory): Boolean { - var bypass = false + // Run the event + val bypassEvent = CAClickResultBypassEvent(event) + Bukkit.getPluginManager().callEvent(bypassEvent) + + var bypass = bypassEvent.isCancelled // Test if disenchantment used event click - if (disenchantmentCompatibility?.testAnvilResult(event, inventory) == true) bypass = true + if (!bypass && (disenchantmentCompatibility?.testAnvilResult(event, inventory) == true)) bypass = true // Test if haven bag used event click if (!bypass && (havenBagsCompatibility?.testAnvilResult(event, inventory) == true)) bypass = true @@ -197,21 +282,45 @@ object DependencyManager { // Test if disenchantment used event click if (!bypass && (excellentEnchantsCompatibility?.testAnvilResult(event) == true)) bypass = true + for (genericDependency in genericDependencies) { + if (!bypass && genericDependency.testAnvilResult(event)) bypass = true + } + // Test if the inventory is a gui(version specific) - if (!bypass && (externGuiTester?.testIfGui(event.view) == true)) bypass = true + if (!bypass && externGuiTester.testIfGui(event.view)) bypass = true + + // Test if in an ax player warp rating gui + if (!bypass && (axPlayerWarpsCompatibility?.testIfGui(event.view.player) == true)) bypass = true return bypass } - fun stripLore(item: ItemStack): ArrayList { - val lore = ArrayList() + // Clone item and use plugin specific clone if needed + fun cloneItem(player: HumanEntity, item: ItemStack): ItemStack { + try { + return unsafeCloneItem(item) + } catch (e: Exception) { + logException(player, e) + return item.clone() + } + } + + private fun unsafeCloneItem(item: ItemStack): ItemStack { + val cloned = itemsAdderCompatibility?.tryClone(item) + if(cloned != null) return cloned + + return item.clone() + } + + fun stripLore(item: ItemStack): MutableList { val dummy = item.clone() enchantmentSquaredCompatibility?.stripLore(dummy) - val itemLore = dummy.itemMeta!!.lore - if (itemLore != null) lore.addAll(itemLore) + val itemLore = dummy.itemMeta?.componentLore() ?: return ArrayList() + val lore = ArrayList() + lore.addAll(itemLore) return lore } @@ -219,13 +328,4 @@ object DependencyManager { enchantmentSquaredCompatibility?.updateLore(item) } - private fun testIsFolia(): Boolean { - try { - Class.forName("io.papermc.paper.threadedregions.RegionizedServer") - return true - } catch (e: ClassNotFoundException) { - return false - } - } - } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/ExcellentEnchantsDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/ExcellentEnchantsDependency.kt deleted file mode 100644 index c1d13d6..0000000 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/ExcellentEnchantsDependency.kt +++ /dev/null @@ -1,157 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency - -import io.delilaheve.CustomAnvil -import org.bukkit.Material -import org.bukkit.event.Listener -import org.bukkit.event.inventory.InventoryClickEvent -import org.bukkit.event.inventory.PrepareAnvilEvent -import org.bukkit.inventory.ItemStack -import org.bukkit.plugin.RegisteredListener -import su.nightexpress.excellentenchants.enchantment.impl.universal.CurseOfFragilityEnchant -import su.nightexpress.excellentenchants.enchantment.listener.AnvilListener -import su.nightexpress.excellentenchants.enchantment.listener.EnchantAnvilListener -import su.nightexpress.excellentenchants.registry.EnchantRegistry -import xyz.alexcrea.cuanvil.api.EnchantmentApi -import xyz.alexcrea.cuanvil.enchant.wrapped.CAEEEnchantment -import xyz.alexcrea.cuanvil.enchant.wrapped.CALegacyEEEnchantment -import java.lang.reflect.Method - -class ExcellentEnchantsDependency { - - private val isModern: Boolean - - init { - CustomAnvil.instance.logger.info("Excellent Enchants Detected !") - - var isModern = true; - try { - Class.forName("su.nightexpress.excellentenchants.enchantment.listener.AnvilListener") - } catch (ignored: ClassNotFoundException) { - isModern = false - } - - this.isModern = isModern - } - - fun registerEnchantments() { - CustomAnvil.instance.logger.info("Preparing Excellent Enchants compatibility...") - - // As excellent enchants is loaded before custom anvil and register enchantment to registry, we need to unregister old "vanilla" enchant. - if (this.isModern) { - for (enchantment in EnchantRegistry.getRegistered()) { - EnchantmentApi.unregisterEnchantment(enchantment.bukkitEnchantment.key) - EnchantmentApi.registerEnchantment(CAEEEnchantment(enchantment)) - } - } else { - for (enchantment in su.nightexpress.excellentenchants.enchantment.registry.EnchantRegistry.getRegistered()) { - EnchantmentApi.unregisterEnchantment(enchantment.enchantment.key) - EnchantmentApi.registerEnchantment(CALegacyEEEnchantment(enchantment)) - } - } - - CustomAnvil.instance.logger.info("Excellent Enchants should now work as expected !") - } - - private var fragilityCurse: CurseOfFragilityEnchant? = null - - private var modernAnvilListener: AnvilListener? = null - private var legacyAnvilListener: EnchantAnvilListener? = null - private lateinit var usedAnvilListener: Listener - - private lateinit var handleRechargeMethod: Method - private lateinit var handleCombineMethod: Method - - fun redirectListeners() { - val toUnregister = ArrayList() - // get required PrepareAnvilEvent listener - for (registeredListener in PrepareAnvilEvent.getHandlerList().registeredListeners) { - val listener = registeredListener.listener - - if (listener is CurseOfFragilityEnchant) { - this.fragilityCurse = listener - toUnregister.add(registeredListener) - } - - if (this.isModern) { - if (listener is AnvilListener) { - this.modernAnvilListener = listener; - toUnregister.add(registeredListener) - } - } else { - if (listener is EnchantAnvilListener) { - this.legacyAnvilListener = listener; - toUnregister.add(registeredListener) - } - } - - } - - for (listener in toUnregister) { - PrepareAnvilEvent.getHandlerList().unregister(listener) - } - - if (this.isModern) { - this.usedAnvilListener = this.modernAnvilListener!! - } else { - this.usedAnvilListener = this.legacyAnvilListener!! - } - - // Unregister inventory click event - InventoryClickEvent.getHandlerList().unregister(this.usedAnvilListener) - - findAnvilFunctions() - } - - private fun findAnvilFunctions() { - this.handleRechargeMethod = this.usedAnvilListener.javaClass.getDeclaredMethod( - "handleRecharge", - PrepareAnvilEvent::class.java, ItemStack::class.java, ItemStack::class.java - ) - this.handleRechargeMethod.setAccessible(true) - - this.handleCombineMethod = this.usedAnvilListener.javaClass.getDeclaredMethod( - "handleCombine", - PrepareAnvilEvent::class.java, ItemStack::class.java, ItemStack::class.java, ItemStack::class.java - ) - this.handleCombineMethod.setAccessible(true) - - } - - fun testPrepareAnvil(event: PrepareAnvilEvent): Boolean { - if (event.result != null) { - this.fragilityCurse?.onItemAnvil(event) - if (event.result == null) return true - } - - val first: ItemStack = treatInput(event.inventory.getItem(0)) - val second: ItemStack = treatInput(event.inventory.getItem(1)) - - return handleRechargeMethod.invoke(this.usedAnvilListener, event, first, second) as Boolean - } - - fun treatAnvilResult(event: PrepareAnvilEvent, result: ItemStack) { - val first: ItemStack = treatInput(event.inventory.getItem(0)) - val second: ItemStack = treatInput(event.inventory.getItem(1)) - - handleCombineMethod.invoke(this.usedAnvilListener, event, first, second, result) - } - - fun testAnvilResult(event: InventoryClickEvent): Any { - if (event.inventory.getItem(2) != null) { - if (this.isModern) { - this.modernAnvilListener!!.onClickAnvil(event) - } else { - this.legacyAnvilListener!!.onClickAnvil(event) - } - return event.inventory.getItem(2) == null - } - - return false; - } - - private fun treatInput(item: ItemStack?): ItemStack { - if (item == null) return ItemStack(Material.AIR) - return item - } - -} diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/MinecraftVersionUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/MinecraftVersionUtil.kt new file mode 100644 index 0000000..cbd2209 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/MinecraftVersionUtil.kt @@ -0,0 +1,58 @@ +package xyz.alexcrea.cuanvil.dependency + +import xyz.alexcrea.cuanvil.update.UpdateUtils + +object MinecraftVersionUtil { + + val craftbukkitVersion: String? + get() { + val version = UpdateUtils.currentMinecraftVersion() + if (version.major != 1) return null + + return when (version.minor) { + 17 -> when (version.patch) { + 0, 1 -> "1_17R1" + else -> null + } + + 18 -> when (version.patch) { + 0, 1 -> "1_18R1" + 2 -> "1_18R2" + else -> null + } + + 19 -> when (version.patch) { + 0, 1, 2 -> "1_19R1" + 3 -> "1_19R2" + 4 -> "1_19R3" + else -> null + } + + 20 -> when (version.patch) { + 0, 1 -> "1_20R1" + 2 -> "1_20R2" + 3, 4 -> "1_20R3" + 5, 6 -> "1_20R4" + else -> null + } + + 21 -> when (version.patch) { + 0, 1 -> "1_21R1" + 2, 3 -> "1_21R2" + 4 -> "1_21R3" + 5 -> "1_21R4" + 6, 7, 8 -> "1_21R5" + 9, 10 -> "1_21R6" + 11 -> "1_21R7" + else -> null + } + + else -> null + } + } + + val isTooNewForSpigot: Boolean get() { + return UpdateUtils.currentMinecraftVersion().major != 1 + } + +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/datapack/DataPackDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/datapack/DataPackDependency.kt new file mode 100644 index 0000000..6d54456 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/datapack/DataPackDependency.kt @@ -0,0 +1,305 @@ +package xyz.alexcrea.cuanvil.dependency.datapack + +import io.delilaheve.CustomAnvil +import org.bukkit.Material +import org.bukkit.NamespacedKey +import org.bukkit.configuration.file.FileConfiguration +import org.bukkit.configuration.file.YamlConfiguration +import xyz.alexcrea.cuanvil.api.ConflictBuilder +import xyz.alexcrea.cuanvil.api.EnchantmentApi +import xyz.alexcrea.cuanvil.api.MaterialGroupApi +import xyz.alexcrea.cuanvil.config.ConfigHolder +import xyz.alexcrea.cuanvil.enchant.wrapped.CABukkitEnchantment +import xyz.alexcrea.cuanvil.enchant.wrapped.CAIncompatibleAllEnchant +import xyz.alexcrea.cuanvil.group.IncludeGroup +import xyz.alexcrea.cuanvil.update.UpdateUtils +import xyz.alexcrea.cuanvil.update.Version +import java.io.InputStreamReader + +object DataPackDependency { + private val START_DETECT_VERSION = Version(1, 20, 5) + + /** + * Map of the latest CustomAnvil update related to the pack + */ + private val LASTEST_VERSION = mapOf( + Pair("bracken", Version(1, 11, 0)), + Pair("enchantplus", Version(1, 13, 0)), + Pair("dungeons_and_taverns", Version(1, 13, 0)) + ) + + val enabledDatapacks: List + get() { + val version: Version = UpdateUtils.currentMinecraftVersion() + if (version.lesserThan(START_DETECT_VERSION)) return emptyList() + + return DataPackTester.enabledPacks + } + + fun handleDatapackConfigs() { + val enabledDatapack = enabledDatapacks + for (packName in enabledDatapack) { + // Handling of pack name is horrible: it is based on file name + // So if someone rename a datapack it will make me sad + if(!packName.startsWith("file/")) continue + + if (packName.contains("bp_post_scarcity", ignoreCase = true) + || packName.contains("bracken", ignoreCase = true)) { + handlePack("bracken") + continue + } + + if (packName.contains("neoenchant", ignoreCase = true)) { + handlePack("enchantplus") + continue + } + + if (packName.contains("Dungeons and Taverns", ignoreCase = true)) { + handlePack("dungeons_and_taverns") + continue + } + + } + } + + private fun handlePack(pack: String){ + CustomAnvil.instance.logger.info("trying to handle datapack $pack") + handlePackInitialConfig(pack) + writeDefaultByNamespace(pack) + CustomAnvil.instance.logger.info("configuration done for $pack") + } + + private fun handlePackInitialConfig(pack: String) { + val defConfig = ConfigHolder.DEFAULT_CONFIG + val version = LASTEST_VERSION[pack] + if(version == null) { + throw RuntimeException("The pack $pack has no latest version hard coded in the plugin") + } + + val currentVersion = Version.fromString(defConfig.config.getString("datapack.$pack")) + if (currentVersion.greaterEqual(version)) { + handleEnchantAllConflict(pack) + return + } + + // Add pack value or do update from previous version + // note: update thingy is not yet implemented + configureDatapack(pack) + + // Finally, set current pack version to config + defConfig.config.set("datapack.$pack", version.toString()) + defConfig.saveToDisk(true) + } + + private fun configureDatapack(pack: String) { + val itemGroups = javaClass.getResource("/datapack/$pack/item_groups.yml") + val itemConflict = javaClass.getResource("/datapack/$pack/item_conflict.yml") + val enchantConflict = javaClass.getResource("/datapack/$pack/enchant_conflict.yml") + + if (itemGroups != null) { + val reader = InputStreamReader(itemGroups.openStream()) + val yml = YamlConfiguration.loadConfiguration(reader) + + handleItemGroups(yml) + } + + val newConflictList = ArrayList() + var needSave = false + if (itemConflict != null) { + val reader = InputStreamReader(itemConflict.openStream()) + val yml = YamlConfiguration.loadConfiguration(reader) + + addItemConflicts(yml, newConflictList) + } + + if (enchantConflict != null) { + val reader = InputStreamReader(enchantConflict.openStream()) + val yml = YamlConfiguration.loadConfiguration(reader) + + needSave = addEnchantConflict(yml, newConflictList) + } + + for (conflict in newConflictList) { + needSave = !conflict.registerIfAbsent() && needSave + } + + if (needSave) { + ConfigHolder.CONFLICT_HOLDER.saveToDisk(true) + } + } + + // Order matter for this file + // Could rewrite to not matter but not really important, so I keep it like that + private fun handleItemGroups(yml: YamlConfiguration) { + for (groupName in yml.getKeys(false)) { + val section = yml.getConfigurationSection(groupName) ?: continue + + var group = MaterialGroupApi.getGroup(groupName) + val exist = group != null + + if (group == null) group = IncludeGroup(groupName) + + for (name in section.getStringList("items")) { + val mat = Material.getMaterial(name.uppercase()) + if (mat == null) { + CustomAnvil.instance.logger.warning("Could not find material $name for item group $groupName") + continue + } + group.addToPolicy(mat.key) + } + for (name in section.getStringList("groups")) { + val otherGroup = MaterialGroupApi.getGroup(name) + if (otherGroup == null) { + CustomAnvil.instance.logger.warning("Could not find sub group $name for group $groupName") + continue + } + + group.addToPolicy(otherGroup) + } + + group.updateMaterials() + + if (exist) { + MaterialGroupApi.writeMaterialGroup(group) + } else { + MaterialGroupApi.addMaterialGroup(group, true) + } + } + } + + private fun addItemConflicts(yml: FileConfiguration, conflictList: MutableList) { + for (ench in yml.getKeys(false)) { + val groups = yml.getStringList(ench) + + val conflict = ConflictBuilder( + "restriction_${ench.replace(":", "_")}", + CustomAnvil.instance + ) + conflict.addEnchantment(NamespacedKey.fromString(ench)!!) + for (group in groups) { + conflict.addExcludedGroup(group) + } + conflict.addExcludedGroup("enchanted_book") + + conflictList.add(conflict) + } + } + + private fun addEnchantConflict(yml: YamlConfiguration, conflictList: MutableList): Boolean { + var needSave = false + + val conflicts = HashMap() + for (ench in yml.getKeys(false)) { + val groups = yml.getStringList(ench) + + for (group in groups) { + if (group.startsWith('#')) { + needSave = joinGroup(conflicts, group.substring(1), ench) || needSave + } else { + createConflict(conflictList, ench, group) + } + } + } + + conflictList.addAll(conflicts.values) + return needSave + } + + private fun createConflict(conflictList: MutableList, ench: String, other: String) { + + val conflict = ConflictBuilder( + "conflict_" + + "${ench.replace(":", "_")}_" + + other.replace(":", "_"), + CustomAnvil.instance + ) + conflict.addEnchantment(NamespacedKey.fromString(ench)!!) + conflict.addEnchantment(NamespacedKey.fromString(other)!!) + + conflict.setMaxBeforeConflict(1) + + conflictList.add(conflict) + } + + private fun setEnchantAsAll(ench: String) { + // We assume current is not null and of type CABukkitEnchantment + val current = EnchantmentApi.getByKey(NamespacedKey.fromString(ench)!!) as CABukkitEnchantment + + // We need to replace current wrapped enchantment with the all conflict wrapper + EnchantmentApi.unregisterEnchantment(current) + EnchantmentApi.registerEnchantment(CAIncompatibleAllEnchant(current.bukkit, current.defaultRarity())) + } + + private fun joinGroup(conflicts: HashMap, group: String, ench: String): Boolean { + if ("all".equals(group, ignoreCase = true)) { + setEnchantAsAll(ench) + return false + } else { + val config = ConfigHolder.CONFLICT_HOLDER.config + + // If conflict do not yet exist + if (!config.isConfigurationSection(group)) { + val conflict = conflicts.getOrPut(group) { + val conflict = ConflictBuilder(group, CustomAnvil.instance) + conflict.setMaxBeforeConflict(1) + conflict + } + + conflict.addEnchantment(NamespacedKey.fromString(ench)!!) + return false + } + // Find current conflict + val manager = ConfigHolder.CONFLICT_HOLDER.conflictManager + + // This assumes that: + // - the conflict existing in the config exist in the runtime config (as configuration section exist) + // - the enchantment exist and is provided correctly + val conflict = manager.conflictList.find { + it.name.equals(group, ignoreCase = true) + } + if(conflict == null) { + // This should not happen as configuration section + CustomAnvil.instance.logger.severe("Could not find $group while its configuration section exist... this should NOT happen") + return false + } + + val key = NamespacedKey.fromString(ench)!! + val enchant = EnchantmentApi.getByKey(key) + if (enchant == null){ + CustomAnvil.instance.logger.severe("Could not find enchantment $ench while configuring pack a datapack") + return false + } + + conflict.addEnchantment(enchant) + + UpdateUtils.addAbsentToList(config, "$group.enchantments", ench) + return true + } + } + + private fun handleEnchantAllConflict(pack: String) { + val enchantConflict = javaClass.getResource("/datapack/$pack/enchant_conflict.yml") ?: return + + val reader = InputStreamReader(enchantConflict.openStream()) + val yml = YamlConfiguration.loadConfiguration(reader) + + for (ench in yml.getKeys(false)) { + val groups = yml.getStringList(ench) + + if (groups.contains("#all")) { + setEnchantAsAll(ench) + } + } + } + + private fun writeDefaultByNamespace(namespace: String) { + for (enchantment in EnchantmentApi.getRegisteredEnchantments().values) { + if(!enchantment.key.namespace.equals(namespace, ignoreCase = true)) continue + + CustomAnvil.log("Writing default for ${enchantment.key}") + EnchantmentApi.writeDefaultConfig(enchantment, false) + } + + } + +} diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/EconomyManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/EconomyManager.kt new file mode 100644 index 0000000..67a6c1e --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/EconomyManager.kt @@ -0,0 +1,32 @@ +package xyz.alexcrea.cuanvil.dependency.economy + +import org.bukkit.entity.Player +import org.bukkit.plugin.Plugin +import java.math.BigDecimal + +interface EconomyManager { + + companion object { + var economy: EconomyManager? = null + + fun setupEconomy(plugin: Plugin) { + if (plugin.server.pluginManager.getPlugin("Vault") == null) + return + if (UnlockedEconomyManager.unlockedAvailable()) + economy = UnlockedEconomyManager(plugin) + + if (economy == null || !economy!!.initialized()) + economy = VaultEconomyManager(plugin) + } + + } + + fun initialized(): Boolean + + // We assume "initialized" got checked before these function get called + fun has(player: Player, money: BigDecimal): Boolean + fun remove(player: Player, money: BigDecimal): Boolean + + fun format(money: BigDecimal): String; + +} diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/UnlockedEconomyManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/UnlockedEconomyManager.kt new file mode 100644 index 0000000..da66f30 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/UnlockedEconomyManager.kt @@ -0,0 +1,74 @@ +package xyz.alexcrea.cuanvil.dependency.economy + +import io.delilaheve.util.ConfigOptions +import net.milkbowl.vault2.economy.Economy +import org.bukkit.entity.Player +import org.bukkit.plugin.Plugin +import java.math.BigDecimal + +class UnlockedEconomyManager : EconomyManager { + + val plugin: String + val economy: Economy? + + companion object { + fun unlockedAvailable(): Boolean { + try { + Class.forName("net.milkbowl.vault2.economy.Economy") + return true + } catch (_: ClassNotFoundException) { + return false + } + } + } + + constructor(plugin: Plugin) { + this.plugin = plugin.name + + val rsp = plugin.server.servicesManager.getRegistration(Economy::class.java) + economy = rsp?.getProvider() + } + + override fun initialized(): Boolean { + return economy != null + } + + private fun currency(): String { + val configured = ConfigOptions.usedCurrency + + return if ("default".equals(configured, true)) + economy!!.getDefaultCurrency(plugin) + else configured + } + + override fun has(player: Player, money: BigDecimal): Boolean { + if (money.signum() <= 0) return true + + return economy!!.has( + plugin, + player.uniqueId, + player.world.name, + currency(), + money + ) + } + + override fun remove(player: Player, money: BigDecimal): Boolean { + if (money.signum() <= 0) return true + + return economy!!.withdraw( + plugin, + player.uniqueId, + player.world.name, + currency(), + money + ) + .transactionSuccess() + } + + override fun format(money: BigDecimal): String { + return economy!!.format(plugin, money, currency()) + } + + +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/VaultEconomyManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/VaultEconomyManager.kt new file mode 100644 index 0000000..058485e --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/VaultEconomyManager.kt @@ -0,0 +1,37 @@ +package xyz.alexcrea.cuanvil.dependency.economy + +import net.milkbowl.vault.economy.Economy +import org.bukkit.entity.Player +import org.bukkit.plugin.Plugin +import java.math.BigDecimal + +class VaultEconomyManager : EconomyManager { + + val economy: Economy? + + constructor(plugin: Plugin) { + val rsp = plugin.server.servicesManager.getRegistration(Economy::class.java) + economy = rsp?.getProvider() + } + + override fun initialized(): Boolean { + return economy != null + } + + override fun has(player: Player, money: BigDecimal): Boolean { + if (money.signum() <= 0) return true + + return economy!!.has(player, money.toDouble()) + } + + override fun remove(player: Player, money: BigDecimal): Boolean { + if (money.signum() <= 0) return true + + return economy!!.withdrawPlayer(player, money.toDouble()).transactionSuccess() + } + + override fun format(money: BigDecimal): String { + return economy!!.format(money.toDouble()) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GenericExternGuiTester.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GenericExternGuiTester.kt new file mode 100644 index 0000000..4046f4a --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GenericExternGuiTester.kt @@ -0,0 +1,111 @@ +package xyz.alexcrea.cuanvil.dependency.gui + +import org.bukkit.inventory.InventoryView +import xyz.alexcrea.cuanvil.dependency.MinecraftVersionUtil +import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil +import java.lang.reflect.Method + +class GenericExternGuiTester { + + companion object { + private const val ANVIL_CLASS_NAME = "org.bukkit.craftbukkit.inventory.view.CraftAnvilView" + private const val INV_CLASS_NAME = "org.bukkit.craftbukkit.inventory.CraftInventoryView" + private const val HANDLE_METHOD_NAME = "getHandle" + + private const val CANONICAL_PAPER_ANVIL_MENU = "net.minecraft.world.inventory.AnvilMenu" + } + + var testExist = false + var inTesting = false + + var testedClass: String? = null + lateinit var getHandleMethod: Method + + private fun getContainerClass(view: InventoryView): Class? { + if(!testedClass.contentEquals(view.javaClass.name)) + return null + + val container = getHandleMethod.invoke(view) + return container.javaClass + } + + fun tryFromClass(className: String) { + val clazz = Class.forName(className) + testedClass = className + + getHandleMethod = clazz.getMethod(HANDLE_METHOD_NAME) + } + + fun isInTest(): Boolean { + if(!testExist) testClassExist() + return inTesting + } + + fun testClassExist() { + testExist = true + + // We first try to get craft anvil interface, + // but is absent on old version so we try craft inventory view before + try { + tryFromClass(ANVIL_CLASS_NAME) + return + } + catch (_: ClassNotFoundException) {} + catch (_: NoSuchMethodException) {} + + try { + tryFromClass(INV_CLASS_NAME) + return + } + catch (_: ClassNotFoundException) {} + catch (_: NoSuchMethodException) {} + + inTesting = true + } + + // Try if were in another plugin anvil inventory + fun testIfGui(inventory: InventoryView): Boolean { + // In case we are in a test environment + if(isInTest()) return false + + val clazz = getContainerClass(inventory) ?: return false + + val clazzName = clazz.name + if(!PlatformUtil.isPaper){ + // Blacklist gui causing issue + if (expectWesjd(clazzName)) return true + if (expectXenondevUI(clazzName)) return true + if (expectVanePortal(clazzName)) return true + + return false + } + + // Only allow cannonical anvil menu class + return !CANONICAL_PAPER_ANVIL_MENU.equals(clazzName, true) + } + + // Known custom implementations + fun expectWesjd(name: String): Boolean { + val expectedWesjdGuiPath = "anvilgui.version.Wrapper${MinecraftVersionUtil.craftbukkitVersion}" + + return name.contains(expectedWesjdGuiPath) + } + + private val XenondevUIPrefix: String + get() = "xyz.xenondevs.inventoryaccess." + private val XenondevUISufix: String + get() = ".AnvilInventoryImpl" + + fun expectXenondevUI(name: String): Boolean { + return name.startsWith(XenondevUIPrefix) + && name.endsWith(XenondevUISufix) + } + + fun expectVanePortal(name: String): Boolean { + val expected = "org.oddlama.vane.core.menu.AnvilMenu\$AnvilContainer" + + return name == expected + } + + +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GuiTesterSelector.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GuiTesterSelector.kt deleted file mode 100644 index b1c27a1..0000000 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GuiTesterSelector.kt +++ /dev/null @@ -1,51 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.gui - -import xyz.alexcrea.cuanvil.dependency.gui.version.*; -import xyz.alexcrea.cuanvil.update.UpdateUtils - -object GuiTesterSelector { - - val selectGuiTester: ExternGuiTester? - get() { - val versionParts = UpdateUtils.currentMinecraftVersionArray() - if (versionParts[0] != 1) return null - - return when (versionParts[1]) { - // Can't support 1.16.5 bc 1.16.5 paper userdev do not exist - - 17 -> when (versionParts[2]) { - 0, 1 -> v1_17R1_ExternGuiTester() - else -> null - } - - 18 -> when (versionParts[2]) { - 0, 1 -> v1_18R1_ExternGuiTester() - 2 -> v1_18R2_ExternGuiTester() - else -> null - } - - 19 -> when (versionParts[2]) { - 0, 1, 2 -> v1_19R1_ExternGuiTester() - 3 -> v1_19R2_ExternGuiTester() - 4 -> v1_19R3_ExternGuiTester() - else -> null - } - - 20 -> when (versionParts[2]) { - 0, 1 -> v1_20R1_ExternGuiTester() - 2 -> v1_20R2_ExternGuiTester() - 3, 4 -> v1_20R3_ExternGuiTester() - 5, 6 -> v1_20R4_ExternGuiTester() - else -> null - } - - 21 -> when (versionParts[2]) { - 0, 1 -> v1_21R1_ExternGuiTester() - else -> null - } - - else -> null - } - } - -} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt index 71f4875..d74ef08 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt @@ -1,16 +1,31 @@ package xyz.alexcrea.cuanvil.dependency.packet import org.bukkit.Bukkit +import xyz.alexcrea.cuanvil.dependency.DependencyManager +import xyz.alexcrea.cuanvil.dependency.MinecraftVersionUtil import xyz.alexcrea.cuanvil.dependency.packet.versions.* import xyz.alexcrea.cuanvil.update.UpdateUtils object PacketManagerSelector { + + private const val PAPER_CRAFT_PLAYER_CLASS = "org.bukkit.craftbukkit.entity.CraftPlayer" + fun selectPacketManager(forceProtocolib: Boolean): PacketManager { // Try to find version + if(DependencyManager.externGuiTester.isInTest()) + return NoPacketManager() + return if (forceProtocolib) protocolibIfPresent - else - versionSpecificManager ?: protocolibIfPresent + else { + try { + Class.forName(PAPER_CRAFT_PLAYER_CLASS) + + return PaperPacketManager() + } catch (_: ClassNotFoundException) { + return reobfPacketManager ?: protocolibIfPresent + } + } } private val protocolibIfPresent: PacketManager @@ -19,48 +34,21 @@ object PacketManagerSelector { ProtocoLibWrapper() else NoPacketManager() - private val versionSpecificManager: PacketManagerBase? + + // Reobfuscated packet manager for spigot or paper as it remap + private val reobfPacketManager: PacketManagerBase? get() { - val versionParts = UpdateUtils.currentMinecraftVersionArray() - if (versionParts[0] != 1) return null + val versionParts = UpdateUtils.currentMinecraftVersion() + if (versionParts.major != 1) return null - return when (versionParts[1]) { - // Can't support 1.16.5 bc 1.16.5 paper userdev do not exist + try { + val clazz = Class.forName("xyz.alexcrea.cuanvil.dependency.packet.versions." + + "V${MinecraftVersionUtil.craftbukkitVersion}_PacketManager") - 17 -> when (versionParts[2]) { - 0, 1 -> V1_17R1_PacketManager() - else -> null - } - - 18 -> when (versionParts[2]) { - 0, 1 -> V1_18R1_PacketManager() - 2 -> V1_18R2_PacketManager() - else -> null - } - - 19 -> when (versionParts[2]) { - 0, 1, 2 -> V1_19R1_PacketManager() - 3 -> V1_19R2_PacketManager() - 4 -> V1_19R3_PacketManager() - else -> null - } - - 20 -> when (versionParts[2]) { - 0, 1 -> V1_20R1_PacketManager() - 2 -> V1_20R2_PacketManager() - 3, 4 -> V1_20R3_PacketManager() - 5, 6 -> V1_20R4_PacketManager() - else -> null - } - - 21 -> when (versionParts[2]) { - 0, 1 -> V1_21R1_PacketManager() - 2, 3 -> V1_21R2_PacketManager() - 4 -> V1_21R3_PacketManager() - else -> null - } - - else -> null + val manager = clazz.getConstructor().newInstance() + return manager as PacketManagerBase + } catch (_: ClassNotFoundException) { + return null } } } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/AxPlayerWarpsDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/AxPlayerWarpsDependency.kt new file mode 100644 index 0000000..e0ca218 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/AxPlayerWarpsDependency.kt @@ -0,0 +1,13 @@ +package xyz.alexcrea.cuanvil.dependency.plugins + +import com.artillexstudios.axplayerwarps.libs.axapi.gui.AnvilInput +import org.bukkit.entity.HumanEntity +import org.bukkit.entity.Player + +class AxPlayerWarpsDependency { + + fun testIfGui(player: HumanEntity): Boolean { + return player is Player && AnvilInput.get(player) != null + } + +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DisenchantmentDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/DisenchantmentDependency.kt similarity index 52% rename from src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DisenchantmentDependency.kt rename to src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/DisenchantmentDependency.kt index 917c268..690b384 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DisenchantmentDependency.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/DisenchantmentDependency.kt @@ -1,18 +1,25 @@ -package xyz.alexcrea.cuanvil.dependency +package xyz.alexcrea.cuanvil.dependency.plugins import com.jankominek.disenchantment.Disenchantment import com.jankominek.disenchantment.events.DisenchantClickEvent import com.jankominek.disenchantment.events.DisenchantEvent import com.jankominek.disenchantment.events.ShatterClickEvent import com.jankominek.disenchantment.events.ShatterEvent +import com.jankominek.disenchantment.listeners.DisenchantClickListener +import com.jankominek.disenchantment.listeners.ShatterClickListener import io.delilaheve.CustomAnvil -import org.bukkit.entity.HumanEntity +import org.bukkit.entity.Player +import org.bukkit.event.Listener import org.bukkit.event.inventory.InventoryClickEvent import org.bukkit.event.inventory.PrepareAnvilEvent import org.bukkit.inventory.AnvilInventory import org.bukkit.inventory.ItemStack +import xyz.alexcrea.cuanvil.anvil.AnvilCost import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener -import xyz.alexcrea.cuanvil.util.AnvilXpUtil +import xyz.alexcrea.cuanvil.util.MetricsUtil.trackError +import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil +import java.util.logging.Level +import kotlin.reflect.KClass class DisenchantmentDependency { @@ -22,25 +29,44 @@ class DisenchantmentDependency { fun redirectListeners() { PrepareAnvilEvent.getHandlerList().unregister(Disenchantment.plugin) - InventoryClickEvent.getHandlerList().unregister(Disenchantment.plugin) + + // unregister only the feature click event and not all + // This is to avoid the disenchantment gui breaking + try { + unregisterStaticDisenchantmentListener(ShatterClickListener::class) + unregisterStaticDisenchantmentListener(DisenchantClickListener::class) + } catch (e: Exception) { + CustomAnvil.instance.logger.log( + Level.SEVERE, "Could not initialize disenchantment support" + + "please report this bug to the developer", e + ) + trackError(e) + } } - fun testPrepareAnvil(event: PrepareAnvilEvent, player: HumanEntity): Boolean { + private fun unregisterStaticDisenchantmentListener(clazz: KClass<*>) { + val field = clazz.java.getDeclaredField("listener") + field.isAccessible = true + val listener: Listener = field.get(null) as Listener + InventoryClickEvent.getHandlerList().unregister(listener) + } + + fun testPrepareAnvil(event: PrepareAnvilEvent, player: Player): Boolean { val previousResult = event.result event.result = null // Test if event change the result DisenchantEvent.onEvent(event) - if(event.result != null) { + if (event.result != null) { CustomAnvil.log("Detected pre anvil item extract bypass.") - AnvilXpUtil.setAnvilInvXp(event.inventory, event.view, player, event.inventory.repairCost) + AnvilXpUtil.setAnvilInvCost(event.inventory, event.view, player, AnvilCost(event.inventory.repairCost)) return true } ShatterEvent.onEvent(event) - if(event.result != null) { + if (event.result != null) { CustomAnvil.log("Detected pre anvil split enchant bypass.") - AnvilXpUtil.setAnvilInvXp(event.inventory, event.view, player, event.inventory.repairCost) + AnvilXpUtil.setAnvilInvCost(event.inventory, event.view, player, AnvilCost(event.inventory.repairCost)) return true } @@ -53,13 +79,13 @@ class DisenchantmentDependency { // Test event if change the result DisenchantClickEvent.onEvent(event) - if(!testAnvilInventoryChange(inventory, previousResultSlot) || event.isCancelled) { + if (!testAnvilInventoryChange(inventory, previousResultSlot) || event.isCancelled) { CustomAnvil.log("Detected anvil click item extract bypass.") return true } ShatterClickEvent.onEvent(event) - if(!testAnvilInventoryChange(inventory, previousResultSlot) || event.isCancelled) { + if (!testAnvilInventoryChange(inventory, previousResultSlot) || event.isCancelled) { CustomAnvil.log("Detected anvil click split enchant bypass.") return true } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/EcoEnchantDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/EcoEnchantDependency.kt similarity index 90% rename from src/main/kotlin/xyz/alexcrea/cuanvil/dependency/EcoEnchantDependency.kt rename to src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/EcoEnchantDependency.kt index ba52e23..079d570 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/EcoEnchantDependency.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/EcoEnchantDependency.kt @@ -1,7 +1,9 @@ -package xyz.alexcrea.cuanvil.dependency +package xyz.alexcrea.cuanvil.dependency.plugins +import com.willfp.eco.core.EcoPlugin import com.willfp.ecoenchants.enchant.EcoEnchant import com.willfp.ecoenchants.enchant.EcoEnchants +import com.willfp.ecoenchants.mechanics.infiniteIfNegative import io.delilaheve.CustomAnvil import org.bukkit.event.inventory.PrepareAnvilEvent import org.bukkit.plugin.Plugin @@ -32,6 +34,10 @@ class EcoEnchantDependency(private val ecoEnchantPlugin: Plugin) { } + public fun getEcoLevelLimit(): Int { + return (ecoEnchantPlugin as EcoPlugin).configYml.getInt("anvil.enchant-limit").infiniteIfNegative() + } + fun disableAnvilListener() { PrepareAnvilEvent.getHandlerList().unregister(this.ecoEnchantPlugin) } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/EcoItemDependencyUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/EcoItemDependencyUtil.kt new file mode 100644 index 0000000..7e84231 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/EcoItemDependencyUtil.kt @@ -0,0 +1,38 @@ +package xyz.alexcrea.cuanvil.dependency.plugins + +import com.willfp.ecoitems.items.EcoItem +import com.willfp.ecoitems.items.EcoItems +import com.willfp.ecoitems.items.ecoItem +import org.bukkit.Material +import org.bukkit.NamespacedKey +import org.bukkit.inventory.ItemStack + +object EcoItemDependencyUtil { + + fun ecoItemNamespace(item: ItemStack): NamespacedKey? { + val ecoi = item.ecoItem ?: return null + + return ecoi.id + } + + fun ecoItemFromKey(key: NamespacedKey): EcoItem? { + return EcoItems.getByID(key.toString()) + } + + fun ecoItemMaterialFromKey(key: NamespacedKey): Material? { + val ecoi = ecoItemFromKey(key) ?: return null + + return ecoi.itemStack.type + } + + fun newEcoItemstack(key: NamespacedKey): ItemStack? { + val ecoi = ecoItemFromKey(key) ?: return null + + return ecoi.itemStack + } + + fun getItems(): List { + return EcoItems.values().map { item -> item.id } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/EnchantmentSquaredDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/EnchantmentSquaredDependency.kt similarity index 87% rename from src/main/kotlin/xyz/alexcrea/cuanvil/dependency/EnchantmentSquaredDependency.kt rename to src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/EnchantmentSquaredDependency.kt index e630e8c..d769986 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/EnchantmentSquaredDependency.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/EnchantmentSquaredDependency.kt @@ -1,4 +1,4 @@ -package xyz.alexcrea.cuanvil.dependency +package xyz.alexcrea.cuanvil.dependency.plugins import io.delilaheve.CustomAnvil import me.athlaeos.enchantssquared.enchantments.CustomEnchant @@ -101,31 +101,17 @@ class EnchantmentSquaredDependency(private val enchantmentSquaredPlugin: Plugin) private fun writeMissingGroups(){ // Write group that do not exist on custom anvil. - // (Tools group regroup most of the tool items. I did not create a seperated group for theses) - val pickaxes = IncludeGroup("pickaxes") - pickaxes.addAll(Material.WOODEN_PICKAXE, Material.STONE_PICKAXE, Material.IRON_PICKAXE, Material.DIAMOND_PICKAXE, Material.GOLDEN_PICKAXE, Material.NETHERITE_PICKAXE) - MaterialGroupApi.addMaterialGroup(pickaxes) - - val shovels = IncludeGroup("shovels") - shovels.addAll(Material.WOODEN_SHOVEL, Material.STONE_SHOVEL, Material.IRON_SHOVEL, Material.DIAMOND_SHOVEL, Material.GOLDEN_SHOVEL, Material.NETHERITE_SHOVEL) - MaterialGroupApi.addMaterialGroup(shovels) - - val hoes = IncludeGroup("hoes") - hoes.addAll(Material.WOODEN_HOE, Material.STONE_HOE, Material.IRON_HOE, Material.DIAMOND_HOE, Material.GOLDEN_HOE, Material.NETHERITE_HOE) - MaterialGroupApi.addMaterialGroup(hoes) - val shield = IncludeGroup("shield") - shield.addToPolicy(Material.SHIELD) + shield.addToPolicy(Material.SHIELD.key) MaterialGroupApi.addMaterialGroup(shield) val elytra = IncludeGroup("elytra") - elytra.addToPolicy(Material.ELYTRA) + elytra.addToPolicy(Material.ELYTRA.key) MaterialGroupApi.addMaterialGroup(elytra) val trinkets = IncludeGroup("trinkets") - trinkets.addToPolicy(Material.ROTTEN_FLESH) + trinkets.addToPolicy(Material.ROTTEN_FLESH.key) MaterialGroupApi.addMaterialGroup(trinkets) - } private fun writeMaterialRestriction(esEnchantments: List){ diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchantsDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchantsDependency.kt new file mode 100644 index 0000000..816a4df --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchantsDependency.kt @@ -0,0 +1,258 @@ +package xyz.alexcrea.cuanvil.dependency.plugins + +import io.delilaheve.CustomAnvil +import org.bukkit.Material +import org.bukkit.event.Listener +import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.event.inventory.PrepareAnvilEvent +import org.bukkit.inventory.ItemStack +import org.bukkit.plugin.RegisteredListener +import xyz.alexcrea.cuanvil.api.EnchantmentApi +import xyz.alexcrea.cuanvil.api.event.listener.CATreatAnvilResult2Event +import xyz.alexcrea.cuanvil.enchant.wrapped.CAEEPreV5Enchantment +import xyz.alexcrea.cuanvil.enchant.wrapped.CAEEV5Enchantment +import xyz.alexcrea.cuanvil.enchant.wrapped.CAEEV5_4Enchantment +import xyz.alexcrea.cuanvil.enchant.wrapped.CALegacyEEEnchantment +import java.lang.reflect.Constructor +import java.lang.reflect.Method +import su.nightexpress.excellentenchants.api.EnchantRegistry as V5EnchantRegistry +import su.nightexpress.excellentenchants.enchantment.impl.universal.CurseOfFragilityEnchant as LegacyCurseOfFragilityEnchant +import su.nightexpress.excellentenchants.manager.listener.AnvilListener as V5AnvilListener +import su.nightexpress.excellentenchants.enchantment.listener.AnvilListener as PreV5AnvilListener +import su.nightexpress.excellentenchants.enchantment.listener.EnchantAnvilListener as LegacyAnvilListener +import su.nightexpress.excellentenchants.enchantment.registry.EnchantRegistry as LegacyEnchantRegistry +import su.nightexpress.excellentenchants.registry.EnchantRegistry as PreV5EnchantRegistry + +// I don't like that I need to support older version. if I could just drop older support it would be sooo nice +class ExcellentEnchantsDependency { + + enum class ListenerVersion(val classPath: String) { + V5_4("su.nightexpress.excellentenchants.enchantment.EnchantSettings"), + V5_3("su.nightexpress.excellentenchants.enchantment.EnchantRegistry"), + V5("su.nightexpress.excellentenchants.manager.listener.AnvilListener"), + PRE_V5("su.nightexpress.excellentenchants.enchantment.listener.AnvilListener"), + LEGACY("su.nightexpress.excellentenchants.enchantment.listener.EnchantAnvilListener"), + } + + private val listenerVersion: ListenerVersion? + private val isModernCurseOfFragility: Boolean + + init { + CustomAnvil.instance.logger.info("Excellent Enchants Detected !") + + var listenerVersion: ListenerVersion? = null + for (value in ListenerVersion.entries) { + try { + Class.forName(value.classPath) + + listenerVersion = value + break + } catch (ignored: ClassNotFoundException) { + } + } + + if (listenerVersion == null) { + CustomAnvil.instance.logger.severe("Found issue with listener of Excellent Enchants. compatiblity is broken. please contact CustomAnvil devs") + } else{ + CustomAnvil.log("Support version: " + listenerVersion.name) + } + + var isModernCurseOfFragility = true + try { + Class.forName("su.nightexpress.excellentenchants.enchantment.universal.CurseOfFragilityEnchant") + } catch (ignored: ClassNotFoundException) { + isModernCurseOfFragility = false + } + + this.listenerVersion = listenerVersion + this.isModernCurseOfFragility = isModernCurseOfFragility + } + + fun registerEnchantments() { + CustomAnvil.instance.logger.info("Preparing Excellent Enchants compatibility...") + + // As excellent enchants is loaded before custom anvil and register enchantment to registry, we need to unregister old "vanilla" enchant. + when (listenerVersion) { + ListenerVersion.V5_4 -> { + for (enchantment in ExcellentEnchant5_3Registry.getRegistered()) { + EnchantmentApi.unregisterEnchantment(enchantment.bukkitEnchantment.key) + EnchantmentApi.registerEnchantment(CAEEV5_4Enchantment(enchantment)) + } + } + + ListenerVersion.V5, ListenerVersion.V5_3 -> { + for (enchantment in V5EnchantRegistry.getRegistered()) { + EnchantmentApi.unregisterEnchantment(enchantment.bukkitEnchantment.key) + EnchantmentApi.registerEnchantment(CAEEV5Enchantment(enchantment)) + } + } + + ListenerVersion.PRE_V5 -> { + for (enchantment in PreV5EnchantRegistry.getRegistered()) { + EnchantmentApi.unregisterEnchantment(enchantment.bukkitEnchantment.key) + EnchantmentApi.registerEnchantment(CAEEPreV5Enchantment(enchantment)) + } + } + + ListenerVersion.LEGACY -> { + for (enchantment in LegacyEnchantRegistry.getRegistered()) { + EnchantmentApi.unregisterEnchantment(enchantment.enchantment.key) + EnchantmentApi.registerEnchantment(CALegacyEEEnchantment(enchantment)) + } + } + + null -> return + + } + + CustomAnvil.instance.logger.info("Excellent Enchants should now work as expected !") + } + + private var legacyFragilityCurse: LegacyCurseOfFragilityEnchant? = null + + private var v5AnvilListener: V5AnvilListener? = null + private var preV5AnvilListener: PreV5AnvilListener? = null + private var legacyAnvilListener: LegacyAnvilListener? = null + private lateinit var usedAnvilListener: Listener + + private lateinit var handleRechargeMethod: Method + private lateinit var handleCombineMethod: Method + + private val prepareAnvilConstructor = PrepareAnvilEvent::class.java.constructors.first() as Constructor + + fun redirectListeners() { + val toUnregister = ArrayList() + // get required PrepareAnvilEvent listener + for (registeredListener in PrepareAnvilEvent.getHandlerList().registeredListeners) { + val listener = registeredListener.listener + + if (!isModernCurseOfFragility) { + if (listener is LegacyCurseOfFragilityEnchant) { + this.legacyFragilityCurse = listener + toUnregister.add(registeredListener) + } + } + + when (listenerVersion) { + ListenerVersion.V5, + ListenerVersion.V5_3, + ListenerVersion.V5_4, + -> { + if (listener is V5AnvilListener) { + this.v5AnvilListener = listener + toUnregister.add(registeredListener) + } + } + + ListenerVersion.PRE_V5 -> { + if (listener is PreV5AnvilListener) { + this.preV5AnvilListener = listener + toUnregister.add(registeredListener) + } + } + + ListenerVersion.LEGACY -> { + if (listener is LegacyAnvilListener) { + this.legacyAnvilListener = listener + toUnregister.add(registeredListener) + } + } + + null -> { + } + } + + } + + for (listener in toUnregister) { + PrepareAnvilEvent.getHandlerList().unregister(listener) + } + + when (listenerVersion) { + ListenerVersion.V5_3, + ListenerVersion.V5, + ListenerVersion.V5_4, + -> this.usedAnvilListener = v5AnvilListener!! + ListenerVersion.PRE_V5 -> this.usedAnvilListener = preV5AnvilListener!! + ListenerVersion.LEGACY -> this.usedAnvilListener = legacyAnvilListener!! + null -> {} + } + + // Unregister inventory click event + InventoryClickEvent.getHandlerList().unregister(this.usedAnvilListener) + + findAnvilFunctions() + } + + private fun findAnvilFunctions() { + this.handleRechargeMethod = this.usedAnvilListener.javaClass.getDeclaredMethod( + "handleRecharge", + PrepareAnvilEvent::class.java, ItemStack::class.java, ItemStack::class.java + ) + this.handleRechargeMethod.setAccessible(true) + + try { + this.handleCombineMethod = this.usedAnvilListener.javaClass.getDeclaredMethod( + "anvilCombine", + PrepareAnvilEvent::class.java, ItemStack::class.java, ItemStack::class.java, ItemStack::class.java + ) + this.handleCombineMethod.setAccessible(true) + } catch (_: NoSuchMethodException) { + this.handleCombineMethod = this.usedAnvilListener.javaClass.getDeclaredMethod( + "handleCombine", + PrepareAnvilEvent::class.java, ItemStack::class.java, ItemStack::class.java, ItemStack::class.java + ) + this.handleCombineMethod.setAccessible(true) + } + + } + + fun testPrepareAnvil(event: PrepareAnvilEvent): Boolean { + if (event.result != null) { + if (!isModernCurseOfFragility) { + this.legacyFragilityCurse?.onItemAnvil(event) + } + if (event.result == null) return true + } + + val first: ItemStack = treatInput(event.inventory.getItem(0)) + val second: ItemStack = treatInput(event.inventory.getItem(1)) + + return handleRechargeMethod.invoke(this.usedAnvilListener, event, first, second) as Boolean + } + + fun treatAnvilResult(event: CATreatAnvilResult2Event) { + val result = event.result ?: return + + val first: ItemStack = treatInput(event.leftItem) + val second: ItemStack = treatInput(event.rightItem) + val fakeEvent = prepareAnvilConstructor.newInstance(event.view, result) + + handleCombineMethod.invoke(this.usedAnvilListener, fakeEvent, first, second, result) + + event.result = fakeEvent.result + } + + fun testAnvilResult(event: InventoryClickEvent): Any { + if (event.inventory.getItem(2) != null) { + when (listenerVersion) { + ListenerVersion.V5, + ListenerVersion.V5_3, + ListenerVersion.V5_4, + -> v5AnvilListener!!.onClickAnvil(event) + ListenerVersion.PRE_V5 -> preV5AnvilListener!!.onClickAnvil(event) + ListenerVersion.LEGACY -> legacyAnvilListener!!.onClickAnvil(event) + null -> {} + } + return event.inventory.getItem(2) == null + } + + return false + } + + private fun treatInput(item: ItemStack?): ItemStack { + if (item == null) return ItemStack(Material.AIR) + return item + } + +} diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/GenericPluginDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/GenericPluginDependency.kt new file mode 100644 index 0000000..62dae9b --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/GenericPluginDependency.kt @@ -0,0 +1,81 @@ +package xyz.alexcrea.cuanvil.dependency.plugins + +import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.event.inventory.PrepareAnvilEvent +import org.bukkit.plugin.Plugin +import org.bukkit.plugin.RegisteredListener + +open class GenericPluginDependency(protected open val plugin: Plugin, private val testPrepare: Boolean = true) { + + private val preAnvil = ArrayList() + private val postAnvil = ArrayList() + + open fun redirectListeners() { + fillPreAnvil(preAnvil) + fillPostAnvil(postAnvil, preAnvil) + + // get required PrepareAnvilEvent listener + for (listener in preAnvil) { + PrepareAnvilEvent.getHandlerList().unregister(listener) + } + + for (listener in postAnvil) { + InventoryClickEvent.getHandlerList().unregister(listener) + } + } + + open fun fillPreAnvil(preAnvil: ArrayList){ + // get PreAnvil and PostAnvil listeners + for (registeredListener in PrepareAnvilEvent.getHandlerList().registeredListeners) { + + if (registeredListener.plugin != plugin) continue + preAnvil.add(registeredListener) + } + } + + protected open fun fillPostAnvil( + postAnvil: ArrayList, + preAnvil: ArrayList) { + + } + + open fun testPrepareAnvil(event: PrepareAnvilEvent): Boolean { + if(!testPrepare) return false + + val previousResult = event.result + event.result = null + + for (registeredListener in preAnvil) { + // We do not want error from another plugin to be our fault + try { + registeredListener.callEvent(event) + } catch (e: Exception) { + e.printStackTrace() + } + + if (event.result != null) return true + } + + event.result = previousResult; + return false + } + + open fun testAnvilResult(event: InventoryClickEvent): Boolean { + if(!testPrepare) return false + + for (registeredListener in postAnvil) { + // We do not want error from another plugin to be our fault + try { + registeredListener.callEvent(event) + } catch (e: Exception) { + e.printStackTrace() + } + + if (event.inventory.getItem(2) == null) return true + } + + return false + } + + +} diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/HavenBagsDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/HavenBagsDependency.kt similarity index 83% rename from src/main/kotlin/xyz/alexcrea/cuanvil/dependency/HavenBagsDependency.kt rename to src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/HavenBagsDependency.kt index e0fd3f9..6f30497 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/HavenBagsDependency.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/HavenBagsDependency.kt @@ -1,7 +1,7 @@ -package xyz.alexcrea.cuanvil.dependency +package xyz.alexcrea.cuanvil.dependency.plugins import io.delilaheve.CustomAnvil -import org.bukkit.entity.HumanEntity +import org.bukkit.entity.Player import org.bukkit.event.inventory.InventoryClickEvent import org.bukkit.event.inventory.PrepareAnvilEvent import org.bukkit.inventory.AnvilInventory @@ -9,8 +9,9 @@ import org.bukkit.plugin.RegisteredListener import valorless.havenbags.HavenBags import valorless.havenbags.features.BagSkin import valorless.havenbags.features.BagUpgrade +import xyz.alexcrea.cuanvil.anvil.AnvilCost import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener -import xyz.alexcrea.cuanvil.util.AnvilXpUtil +import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil class HavenBagsDependency { @@ -45,7 +46,7 @@ class HavenBagsDependency { } - fun testPrepareAnvil(event: PrepareAnvilEvent, player: HumanEntity): Boolean { + fun testPrepareAnvil(event: PrepareAnvilEvent, player: Player): Boolean { val previousResult = event.result event.result = null @@ -53,14 +54,14 @@ class HavenBagsDependency { bagSkin.onPrepareAnvil(event) if (event.result != null) { CustomAnvil.log("Detected pre anvil heaven bag anvil skin.") - AnvilXpUtil.setAnvilInvXp(event.inventory, event.view, player, event.inventory.repairCost) + AnvilXpUtil.setAnvilInvCost(event.inventory, event.view, player, AnvilCost(event.inventory.repairCost)) return true } bagUpgrade.onPrepareAnvil(event) if (event.result != null) { CustomAnvil.log("Detected pre anvil heaven bag anvil upgrade.") - AnvilXpUtil.setAnvilInvXp(event.inventory, event.view, player, event.inventory.repairCost) + AnvilXpUtil.setAnvilInvCost(event.inventory, event.view, player, AnvilCost(event.inventory.repairCost)) return true } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ItemsAdderDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ItemsAdderDependency.kt new file mode 100644 index 0000000..7f977e1 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ItemsAdderDependency.kt @@ -0,0 +1,42 @@ +package xyz.alexcrea.cuanvil.dependency.plugins + +import dev.lone.itemsadder.api.CustomStack +import dev.lone.itemsadder.api.ItemsAdder +import org.bukkit.NamespacedKey +import org.bukkit.inventory.ItemStack +import org.bukkit.plugin.Plugin + +class ItemsAdderDependency(plugin: Plugin) : GenericPluginDependency(plugin) { + var isLoaded: Boolean = false + get() { + if (field) return true + + // We can't be sure the event is registered before being triggered so we need to use this function + field = ItemsAdder.areItemsLoaded() + return field + } + + fun tryClone(item: ItemStack): ItemStack? { + if(!isLoaded) return null + val customItem = CustomStack.byItemStack(item) ?: return null + + return CustomStack.getInstance(customItem.namespacedID)?.itemStack + } + + fun fromKey(key: NamespacedKey): ItemStack? { + if(!isLoaded) return null + return CustomStack.getInstance(key.toString())?.itemStack + } + + fun getKey(item: ItemStack) : NamespacedKey? { + if(!isLoaded) return null + val customItem = CustomStack.byItemStack(item) ?: return null + + return NamespacedKey.fromString(customItem.namespacedID) + } + + fun idsCount(): Set { + return CustomStack.getNamespacedIdsInRegistry() + } + +} diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/LegacyEcoEnchantDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/LegacyEcoEnchantDependency.kt similarity index 97% rename from src/main/kotlin/xyz/alexcrea/cuanvil/dependency/LegacyEcoEnchantDependency.kt rename to src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/LegacyEcoEnchantDependency.kt index 5e82e5c..60f00b8 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/LegacyEcoEnchantDependency.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/LegacyEcoEnchantDependency.kt @@ -1,4 +1,4 @@ -package xyz.alexcrea.cuanvil.dependency +package xyz.alexcrea.cuanvil.dependency.plugins import com.willfp.ecoenchants.enchantments.EcoEnchant import com.willfp.ecoenchants.enchantments.EcoEnchants diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/SuperEnchantDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/SuperEnchantDependency.kt new file mode 100644 index 0000000..11622f9 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/SuperEnchantDependency.kt @@ -0,0 +1,91 @@ +package xyz.alexcrea.cuanvil.dependency.plugins + +import com.maddoxh.superEnchants.SuperEnchants +import com.maddoxh.superEnchants.enchants.EnchantManager +import com.maddoxh.superEnchants.listeners.AnvilListener +import io.delilaheve.CustomAnvil +import org.bukkit.command.Command +import org.bukkit.command.CommandExecutor +import org.bukkit.command.CommandSender +import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.plugin.RegisteredListener +import xyz.alexcrea.cuanvil.api.EnchantmentApi +import xyz.alexcrea.cuanvil.enchant.bulk.SuperEnchantBulkOperation +import xyz.alexcrea.cuanvil.enchant.wrapped.CASuperEnchantEnchantment +import java.util.logging.Level + +class SuperEnchantDependency(override val plugin: SuperEnchants): GenericPluginDependency(plugin, false) { + + lateinit var enchManager: EnchantManager + val enchantments = ArrayList() + + fun registerEnchantments(): Boolean{ + CustomAnvil.instance.logger.info("Preparing Super Enchant compatibility...") + + val field = SuperEnchants::class.java.getDeclaredField("enchantManager") + if(field == null) { + CustomAnvil.instance.logger.log(Level.SEVERE, "Failed to initialize Super Enchant compatibility") + return false + } + field.setAccessible(true) + + val bulkOpperations = SuperEnchantBulkOperation(plugin) + EnchantmentApi.addBulkGet(bulkOpperations) + EnchantmentApi.addBulkClean(bulkOpperations) + + enchManager = field.get(plugin) as EnchantManager + overrideReloadCommand() + + reload() + return true + } + + fun reload() { + for (enchantment in enchantments) { + EnchantmentApi.unregisterEnchantment(enchantment) + } + enchantments.clear() + + // Register enchantments + for (enchant in enchManager.getAll()) { + val enchantment = CASuperEnchantEnchantment(enchant, plugin, enchManager) + enchantments.add(enchantment) + + EnchantmentApi.registerEnchantment(enchantment) + } + } + + private fun overrideReloadCommand() { + val reload = CustomAnvil.instance.getCommand("sereload") + + reload?.setExecutor(ReloadInterceptor(reload.executor)) + } + + inner class ReloadInterceptor(val other: CommandExecutor): CommandExecutor { + + override fun onCommand( + sender: CommandSender, + command: Command, + label: String, + args: Array + ): Boolean { + val result = other.onCommand(sender, command, label, args) + + CustomAnvil.log("Detected SuperEnchant reload") + reload() + + return result + } + + } + + override fun fillPostAnvil(postAnvil: ArrayList, preAnvil: ArrayList) { + + for (registeredListener in InventoryClickEvent.getHandlerList().registeredListeners) { + + if (registeredListener.listener.javaClass != AnvilListener::class.java) continue + postAnvil.add(registeredListener) + } + } + +} diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ToolStatsDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ToolStatsDependency.kt new file mode 100644 index 0000000..255f737 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ToolStatsDependency.kt @@ -0,0 +1,39 @@ +package xyz.alexcrea.cuanvil.dependency.plugins + +import lol.hyper.toolstats.ToolStats +import lol.hyper.toolstats.tools.ItemChecker +import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.inventory.ItemStack +import org.bukkit.plugin.Plugin +import org.bukkit.plugin.RegisteredListener +import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener +import java.lang.reflect.Method + +class ToolStatsDependency(plugin: Plugin) : GenericPluginDependency(plugin) { + + // Sadly, getTokens function is private, so I need to do some reflectino + private val getTokenMethod: Method = + ItemChecker::class.java.getDeclaredMethod("getTokens", ItemStack::class.java); + + init { + getTokenMethod.trySetAccessible() + } + + private fun ItemChecker.getTokenSafe(item: ItemStack?): Array { + if (item == null) return arrayOf() + return getTokenMethod.invoke(this, item) as Array + } + + override fun testAnvilResult(event: InventoryClickEvent): Boolean { + // Check if token changes from left with result + val left = event.inventory.getItem(PrepareAnvilListener.ANVIL_INPUT_LEFT) + val result = event.inventory.getItem(PrepareAnvilListener.ANVIL_OUTPUT_SLOT) + + val itemChecker = (plugin as ToolStats).itemChecker + + val leftTokens = itemChecker.getTokenSafe(left) + val resultToken = itemChecker.getTokenSafe(result) + + return !leftTokens.contentDeepEquals(resultToken); + } +} diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/scheduler/BukkitScheduler.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/scheduler/BukkitScheduler.kt index b26dd42..8c04162 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/scheduler/BukkitScheduler.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/scheduler/BukkitScheduler.kt @@ -6,12 +6,12 @@ import org.bukkit.plugin.Plugin class BukkitScheduler : TaskScheduler { - override fun scheduleGlobally(plugin: Plugin, task: Runnable, time: Long): Any? { + override fun scheduleGlobally(plugin: Plugin, task: Runnable, time: Long): Any { return Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, task, time) } - override fun scheduleOnEntity(plugin: Plugin, entity: Entity, task: Runnable, time: Long): Any? { + override fun scheduleOnEntity(plugin: Plugin, entity: Entity, task: Runnable, time: Long): Any { return Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, task, time) } } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/group/AbstractMaterialGroup.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/group/AbstractMaterialGroup.kt index ec6e7bc..d0d2bda 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/group/AbstractMaterialGroup.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/group/AbstractMaterialGroup.kt @@ -1,7 +1,8 @@ package xyz.alexcrea.cuanvil.group import org.bukkit.Material -import java.util.* +import org.bukkit.NamespacedKey +import xyz.alexcrea.cuanvil.util.MaterialUtil abstract class AbstractMaterialGroup(private val name: String) { protected val includedMaterial by lazy { createDefaultSet() } @@ -9,12 +10,12 @@ abstract class AbstractMaterialGroup(private val name: String) { /** * Get the group default set */ - protected abstract fun createDefaultSet(): EnumSet + protected abstract fun createDefaultSet(): MutableSet /** * Get if a material is allowed following the group policy */ - open fun contain(mat: Material): Boolean { + open fun contain(mat: NamespacedKey): Boolean { return mat in getMaterials() } @@ -27,13 +28,13 @@ abstract class AbstractMaterialGroup(private val name: String) { * Push a material to this group to follow this group policy * @return this instance. */ - abstract fun addToPolicy(mat: Material): AbstractMaterialGroup + abstract fun addToPolicy(type: NamespacedKey): AbstractMaterialGroup /** * Push a list of material to this group to follow this group policy * @return this instance. */ - fun addAll(vararg materials: Material): AbstractMaterialGroup { + fun addAll(vararg materials: NamespacedKey): AbstractMaterialGroup { for (material in materials) { addToPolicy(material) } @@ -60,19 +61,19 @@ abstract class AbstractMaterialGroup(private val name: String) { /** * Get the group contained material as a set */ - abstract fun getMaterials(): EnumSet + abstract fun getMaterials(): Set /** * Get the group non-inherited material as a set */ - open fun getNonGroupInheritedMaterials(): EnumSet { + open fun getNonGroupInheritedMaterials(): Set { return includedMaterial } /** * Get the group non-inherited material as a set */ - open fun setNonGroupInheritedMaterials(materials: EnumSet) { + open fun setNonGroupInheritedMaterials(materials: Set) { this.includedMaterial.clear() this.includedMaterial.addAll(materials) } @@ -102,8 +103,9 @@ abstract class AbstractMaterialGroup(private val name: String) { // Test inner material val matIterator = includedMaterial.iterator() while (matIterator.hasNext()) { - val material = matIterator.next() - if (material.isAir) continue + val key = matIterator.next() + val material = MaterialUtil.getMatFromKey(key) + if (material == null || material.isAir) continue return material } // Test included group representative material diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictGroup.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictGroup.kt index 56e923f..59841ac 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictGroup.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictGroup.kt @@ -2,15 +2,18 @@ package xyz.alexcrea.cuanvil.group import io.delilaheve.CustomAnvil import org.bukkit.Material +import org.bukkit.NamespacedKey import xyz.alexcrea.cuanvil.enchant.CAEnchantment class EnchantConflictGroup( val name: String, private val cantConflict: AbstractMaterialGroup, - var minBeforeBlock: Int + var minBeforeBlock: Int, ) { private val enchantments = HashSet() + private val conflictsAfterLevel = HashMap() + private val conflictsBeforeLevel = HashMap() fun addEnchantment(enchant: CAEnchantment) { enchantments.add(enchant) @@ -19,19 +22,56 @@ class EnchantConflictGroup( enchantments.addAll(enchants) } - fun allowed(enchants: Set, mat: Material): Boolean { + private fun canBypassByBeforeLevel(enchants: Map): Boolean { + // Either there no "conflict after" + if(conflictsAfterLevel.isEmpty()) return false + + // Or we check if any conflict after enchantment is true + for (entry in conflictsAfterLevel) { + val current = enchants.getOrDefault(entry.key, 0) + if(current > entry.value) + return false + } + + return true + } + + private fun canBypassByAfterLevel(enchants: Map): Boolean { + // Either there no "conflict after" + if(conflictsBeforeLevel.isEmpty()) return false + + // Or we check if any conflict after enchantment is true + for (entry in conflictsBeforeLevel) { + val current = enchants.getOrDefault(entry.key, 0) + if(current < entry.value) + return false + } + + return true + } + + private fun canBypassConflictByLevel(enchants: Map): Boolean { + return canBypassByBeforeLevel(enchants) || canBypassByAfterLevel(enchants) + } + + fun allowed(enchants: Map, mat: NamespacedKey): Boolean { if (enchantments.size < minBeforeBlock) { CustomAnvil.verboseLog("Conflicting bc of to many enchantments") return true } - if (cantConflict.contain(mat)) { + if (cantConflict.contain(mat)) + return true + + // If empty we skip. else we + if(canBypassConflictByLevel(enchants)) return true - } // Count the amount of enchantment that are in the list var enchantAmount = 0 - for (enchantment in enchants) { + for (entry in enchants) { + val enchantment = entry.key + if (enchantment !in enchantments) continue CustomAnvil.verboseLog("Enchant ${enchantment.key} is in: ${enchantAmount + 1}/$minBeforeBlock ") if (++enchantAmount > minBeforeBlock) { @@ -56,6 +96,36 @@ class EnchantConflictGroup( enchantments.addAll(enchants) } + fun getConflictAfters(): HashMap { + return conflictsAfterLevel + } + + fun putConflictAfterLevel(enchantment: CAEnchantment, level: Int): Boolean { + return null != ( + if(level < 0) conflictsAfterLevel.remove(enchantment) + else conflictsAfterLevel.put(enchantment, level)) + } + + fun setConflictsAfterLevel(conflictAfterLevel: HashMap) { + this.conflictsAfterLevel.clear() + this.conflictsAfterLevel.putAll(conflictAfterLevel) + } + + fun getConflictsBefore(): HashMap { + return conflictsBeforeLevel + } + + fun putConflictsBeforeLevel(enchantment: CAEnchantment, level: Int): Boolean { + return null != ( + if(level < 0) conflictsBeforeLevel.remove(enchantment) + else conflictsBeforeLevel.put(enchantment, level)) + } + + fun setConflictsBeforeLevel(conflictBeforeLevel: HashMap) { + this.conflictsBeforeLevel.clear() + this.conflictsBeforeLevel.putAll(conflictBeforeLevel) + } + fun getRepresentativeMaterial(): Material { val groups = getCantConflictGroup().getGroups() val groupIterator = groups.iterator() diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictManager.kt index 1273c8e..38d5476 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictManager.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictManager.kt @@ -8,7 +8,9 @@ import org.bukkit.inventory.ItemStack import xyz.alexcrea.cuanvil.enchant.AdditionalTestEnchantment import xyz.alexcrea.cuanvil.enchant.CAEnchantment import xyz.alexcrea.cuanvil.enchant.CAEnchantmentRegistry +import xyz.alexcrea.cuanvil.util.MaterialUtil.customType import java.util.* +import kotlin.collections.set class EnchantConflictManager { @@ -16,6 +18,14 @@ class EnchantConflictManager { // Path for the enchantments list const val ENCH_LIST_PATH = "enchantments" + // Path for list of enchantments conflicting before level + //TODO add test and gui + const val CONFLICT_AFTER_LEVEL_LIST_PATH = "conflict_after_level" + + // Path for list of enchantments conflicting before level + //TODO add test and gui + const val CONFLICT_BEFORE_LEVEL_LIST_PATH = "conflict_before_level" + // Path for group list related to the conflict const val CONFLICT_GROUP_PATH = "notAffectedGroups" @@ -31,13 +41,19 @@ class EnchantConflictManager { // 1.20.5 compatibility TODO better update system private val SWEEPING_EDGE_ENCHANT = Collections.singletonList( - CAEnchantment.getByKey(NamespacedKey.minecraft("sweeping_edge")) ?: - CAEnchantment.getByKey(Enchantment.SWEEPING_EDGE.key)) + CAEnchantment.getByKey(NamespacedKey.minecraft("sweeping_edge")) + ?: CAEnchantment.getByKey(Enchantment.SWEEPING_EDGE.key) + ) } lateinit var conflictList: ArrayList + + private fun warnBadKey(key: String) { + CustomAnvil.instance.logger.warning("Invalid key $key for conflict: is not a conflict") + } + // Read and prepare all conflict fun prepareConflicts(config: ConfigurationSection, itemManager: ItemGroupManager) { conflictList = ArrayList() @@ -46,10 +62,14 @@ class EnchantConflictManager { for (enchant in CAEnchantmentRegistry.getInstance().values()) { enchant.clearConflict() } - + val keys = config.getKeys(false) for (key in keys) { - val section = config.getConfigurationSection(key)!! + val section = config.getConfigurationSection(key) + if(section == null) { + warnBadKey(key) + continue + } val conflict = createConflict(section, itemManager, key) addConflict(conflict) @@ -57,12 +77,12 @@ class EnchantConflictManager { } - fun addConflict(conflict: EnchantConflictGroup){ + fun addConflict(conflict: EnchantConflictGroup) { addConflictToEnchantments(conflict) conflictList.add(conflict) } - fun removeConflict(conflict: EnchantConflictGroup){ + fun removeConflict(conflict: EnchantConflictGroup) { removeConflictFromEnchantments(conflict) conflictList.remove(conflict) } @@ -109,19 +129,46 @@ class EnchantConflictManager { } } + val conflictsAfterLevel = section.getConfigurationSection(CONFLICT_AFTER_LEVEL_LIST_PATH) + val conflictsAfterMap = conflict.getConflictAfters() + fetchConditionalRestriction(conflictsAfterMap, conflictsAfterLevel, conflictName) + + val conflictsBeforeLevel = section.getConfigurationSection(CONFLICT_BEFORE_LEVEL_LIST_PATH) + val conflictsBeforeMap = conflict.getConflictsBefore() + fetchConditionalRestriction(conflictsBeforeMap, conflictsBeforeLevel, conflictName) + return conflict } + private fun fetchConditionalRestriction(restrictions: MutableMap, section: ConfigurationSection?, conflictName: String) { + if(section == null) return + for (enchantName in section.getKeys(false)) { + val enchants = getEnchantByIdentifier(enchantName) + if (enchants.isEmpty()) { + CustomAnvil.instance.logger.warning("Enchantment $enchantName do not exist but was asked for conditional restriction for conflict $conflictName") + continue + } + + val value = section.getInt(enchantName, -1) + if(value < 0) continue + + for (enchant in enchants) { + val previous = restrictions.getOrDefault(enchant, value) + restrictions[enchant] = value.coerceAtMost(previous) + } + } + } + private fun getEnchantByIdentifier(enchantName: String): List { val key = NamespacedKey.fromString(enchantName) - if(key != null){ + if (key != null) { val enchantment = CAEnchantment.getByKey(key) - if(enchantment != null) return Collections.singletonList(enchantment) + if (enchantment != null) return Collections.singletonList(enchantment) } // Temporary solution for 1.20.5 - when(enchantName){ + when (enchantName) { "minecraft:sweeping", "sweeping", "minecraft:sweeping_edge", "sweeping_edge" -> { return SWEEPING_EDGE_ENCHANT @@ -169,22 +216,31 @@ class EnchantConflictManager { return group } - fun isConflicting(appliedEnchants: Map, item: ItemStack, newEnchant: CAEnchantment): ConflictType { - val mat = item.type - CustomAnvil.verboseLog("Testing conflict for ${newEnchant.key} on ${mat.key}") + fun isConflicting( + appliedEnchants: Map, + item: ItemStack, + newEnchant: CAEnchantment + ): ConflictType { + val type = item.customType + CustomAnvil.verboseLog("Testing conflict for ${newEnchant.key} on ${type}") val conflictList = newEnchant.conflicts var result = ConflictType.NO_CONFLICT for (conflict in conflictList) { - CustomAnvil.verboseLog("Is against $conflict") - val allowed = conflict.allowed(appliedEnchants.keys, mat) + val isBigConflict = conflict.getEnchants().size > 1 + if (result == ConflictType.ITEM_CONFLICT && !isBigConflict) { + CustomAnvil.verboseLog("skipping small conflict ${conflict.name}") + continue + } + + val allowed = conflict.allowed(appliedEnchants, type) CustomAnvil.verboseLog("Was against $conflict and conflicting: ${!allowed} ") if (!allowed) { if (conflict.getEnchants().size <= 1) { result = ConflictType.ITEM_CONFLICT - CustomAnvil.verboseLog("Small conflict, continuing") + CustomAnvil.verboseLog("Small conflict (${conflict.name}), continuing") } else { - CustomAnvil.verboseLog("Big conflict, probably stoping") + CustomAnvil.verboseLog("Big conflict (${conflict.name}), stopping") return ConflictType.ENCHANTMENT_CONFLICT } } @@ -192,19 +248,20 @@ class EnchantConflictManager { val immutableEnchants = Collections.unmodifiableMap(appliedEnchants) for (appliedEnchant in appliedEnchants.keys) { - if(appliedEnchant is AdditionalTestEnchantment){ - val doConflict = appliedEnchant.isEnchantConflict(immutableEnchants, mat) - if(doConflict){ + if (appliedEnchant is AdditionalTestEnchantment) { + val doConflict = appliedEnchant.isEnchantConflict(immutableEnchants, type) + if (doConflict) { + CustomAnvil.verboseLog("Big conflict by additional test, stopping") return ConflictType.ENCHANTMENT_CONFLICT } } } - if((result != ConflictType.ITEM_CONFLICT) && (newEnchant is AdditionalTestEnchantment)){ + if ((result != ConflictType.ITEM_CONFLICT) && (newEnchant is AdditionalTestEnchantment)) { val partialItem = createPartialResult(item, immutableEnchants) - if(newEnchant.isItemConflict(immutableEnchants, mat, partialItem)){ + if (newEnchant.isItemConflict(immutableEnchants, type, partialItem)) { return ConflictType.ITEM_CONFLICT } @@ -214,14 +271,14 @@ class EnchantConflictManager { } private fun createPartialResult(item: ItemStack, enchantments: Map): ItemStack { - val newItem = item.clone() + val newItem = item.clone() - CAEnchantment.clearEnchants(newItem) - enchantments.forEach{ - enchantment -> enchantment.key.addEnchantmentUnsafe(newItem, enchantment.value) - } + CAEnchantment.clearEnchants(newItem) + enchantments.forEach { enchantment -> + enchantment.key.addEnchantmentUnsafe(newItem, enchantment.value) + } - return newItem + return newItem } } @@ -247,7 +304,7 @@ enum class ConflictType(private val importance: Int) { ENCHANTMENT_CONFLICT(2); fun getWorstConflict(otherConflict: ConflictType): ConflictType { - return if(this.importance > otherConflict.importance) this + return if (this.importance > otherConflict.importance) this else otherConflict } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/group/ExcludeGroup.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/group/ExcludeGroup.kt index 7684c3f..d752db5 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/group/ExcludeGroup.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/group/ExcludeGroup.kt @@ -1,11 +1,12 @@ package xyz.alexcrea.cuanvil.group -import org.bukkit.Material +import org.bukkit.NamespacedKey import java.util.* class ExcludeGroup(name: String) : AbstractMaterialGroup(name) { - override fun createDefaultSet(): EnumSet { - return EnumSet.allOf(Material::class.java) + + override fun createDefaultSet(): MutableSet { + return NegativeMaterialSet() } private var includedGroup: MutableSet = HashSet() @@ -20,9 +21,9 @@ class ExcludeGroup(name: String) : AbstractMaterialGroup(name) { return false } - override fun addToPolicy(mat: Material): ExcludeGroup { - includedMaterial.remove(mat) - groupItems.remove(mat) + override fun addToPolicy(type: NamespacedKey): ExcludeGroup { + includedMaterial.remove(type) + groupItems.remove(type) return this } @@ -60,7 +61,7 @@ class ExcludeGroup(name: String) : AbstractMaterialGroup(name) { } } - override fun getMaterials(): EnumSet { + override fun getMaterials(): MutableSet { return groupItems } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/group/IncludeGroup.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/group/IncludeGroup.kt index 848789f..fc9614b 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/group/IncludeGroup.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/group/IncludeGroup.kt @@ -1,11 +1,12 @@ package xyz.alexcrea.cuanvil.group import org.bukkit.Material +import org.bukkit.NamespacedKey import java.util.* class IncludeGroup(name: String) : AbstractMaterialGroup(name) { - override fun createDefaultSet(): EnumSet { - return EnumSet.noneOf(Material::class.java) + override fun createDefaultSet(): MutableSet { + return HashSet() } private var includedGroup: MutableSet = HashSet() @@ -20,9 +21,9 @@ class IncludeGroup(name: String) : AbstractMaterialGroup(name) { return false } - override fun addToPolicy(mat: Material): IncludeGroup { - includedMaterial.add(mat) - groupItems.add(mat) + override fun addToPolicy(type: NamespacedKey): IncludeGroup { + includedMaterial.add(type) + groupItems.add(type) return this } @@ -47,7 +48,7 @@ class IncludeGroup(name: String) : AbstractMaterialGroup(name) { } } - override fun setNonGroupInheritedMaterials(materials: EnumSet) { + override fun setNonGroupInheritedMaterials(materials: Set) { super.setNonGroupInheritedMaterials(materials) updateMaterials() @@ -66,7 +67,7 @@ class IncludeGroup(name: String) : AbstractMaterialGroup(name) { } } - override fun getMaterials(): EnumSet { + override fun getMaterials(): MutableSet { return groupItems } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/group/ItemGroupManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/group/ItemGroupManager.kt index 65eef34..348d0ff 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/group/ItemGroupManager.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/group/ItemGroupManager.kt @@ -31,6 +31,8 @@ class ItemGroupManager { for (key in keys) { if (groupMap.containsKey(key)) continue + if (!config.isConfigurationSection(key)) + continue createGroup(config, keys, key) } } @@ -51,6 +53,7 @@ class ItemGroupManager { key: String ): AbstractMaterialGroup { val groupSection = config.getConfigurationSection(key)!! + val groupType = groupSection.getString(GROUP_TYPE_PATH, null) // Create Material group according to the group type @@ -91,7 +94,7 @@ class ItemGroupManager { } continue } - group.addToPolicy(material) + group.addToPolicy(material.key) } // Read group to include in this group policy. @@ -105,11 +108,13 @@ class ItemGroupManager { continue } // Get other group or create it if not yet created - val otherGroup = if (!groupMap.containsKey(groupName)) { + val otherGroup = + if (!groupMap.containsKey(groupName)) { + if(!config.isConfigurationSection(groupName)) continue createGroup(config, keys, groupName) - } else { - groupMap[groupName]!! } + else groupMap[groupName]!! + // Avoid self reference or it will create an infinite loop if (otherGroup.isReferencing(group)) { CustomAnvil.instance.logger.warning( diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/group/NegativeMaterialSet.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/group/NegativeMaterialSet.kt new file mode 100644 index 0000000..d87004d --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/group/NegativeMaterialSet.kt @@ -0,0 +1,22 @@ +package xyz.alexcrea.cuanvil.group + +import org.bukkit.NamespacedKey +import xyz.alexcrea.cuanvil.util.MaterialUtil +import xyz.alexcrea.cuanvil.util.NegativeSet + +class NegativeMaterialSet: NegativeSet() { + + override fun iterator(): MutableIterator { + val materials = MaterialUtil.getMaterials() + materials.removeIf { negate.contains(it) } + + return materials.iterator() + } + + override fun isEmpty(): Boolean { + return negate.size >= MaterialUtil.getMaterialCount() + } + + override val size get() = MaterialUtil.getMaterialCount() - negate.size + +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilCloseListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilCloseListener.kt index 60a0339..d707b56 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilCloseListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilCloseListener.kt @@ -7,6 +7,7 @@ import org.bukkit.event.Listener import org.bukkit.event.inventory.InventoryCloseEvent import org.bukkit.inventory.AnvilInventory import xyz.alexcrea.cuanvil.dependency.packet.PacketManager +import xyz.alexcrea.cuanvil.util.dialog.AnvilRenameDialogUtil class AnvilCloseListener(private val packetManager: PacketManager) : Listener { @@ -18,6 +19,7 @@ class AnvilCloseListener(private val packetManager: PacketManager) : Listener { packetManager.setInstantBuild(player, false) } + AnvilRenameDialogUtil.anvilRenameDialog.closeInventory(player) } } \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt index e43ff62..e393dbd 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt @@ -3,7 +3,6 @@ package xyz.alexcrea.cuanvil.listener import io.delilaheve.CustomAnvil import io.delilaheve.util.ConfigOptions import io.delilaheve.util.ItemUtil.canMergeWith -import io.delilaheve.util.ItemUtil.unitRepair import org.bukkit.GameMode import org.bukkit.Material import org.bukkit.entity.Player @@ -16,20 +15,26 @@ import org.bukkit.inventory.AnvilInventory import org.bukkit.inventory.InventoryView import org.bukkit.inventory.ItemStack import org.bukkit.inventory.meta.BookMeta +import xyz.alexcrea.cuanvil.anvil.AnvilCost +import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic +import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.AnvilResult +import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.CustomCraftResult +import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.LoreEditResult +import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.UnitRepairResult import xyz.alexcrea.cuanvil.dependency.DependencyManager +import xyz.alexcrea.cuanvil.dependency.economy.EconomyManager +import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil.setComponentDisplayName import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener.Companion.ANVIL_INPUT_LEFT import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener.Companion.ANVIL_INPUT_RIGHT import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener.Companion.ANVIL_OUTPUT_SLOT -import xyz.alexcrea.cuanvil.recipe.AnvilCustomRecipe -import xyz.alexcrea.cuanvil.util.AnvilLoreEditUtil -import xyz.alexcrea.cuanvil.util.AnvilUseType -import xyz.alexcrea.cuanvil.util.AnvilXpUtil import xyz.alexcrea.cuanvil.util.CustomRecipeUtil -import xyz.alexcrea.cuanvil.util.UnitRepairUtil.getRepair +import xyz.alexcrea.cuanvil.util.MiniMessageUtil +import xyz.alexcrea.cuanvil.util.anvil.AnvilLoreEditUtil +import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil import xyz.alexcrea.cuanvil.util.config.LoreEditType import java.util.* -import java.util.concurrent.atomic.AtomicInteger +import java.util.concurrent.atomic.AtomicReference import kotlin.math.min class AnvilResultListener : Listener { @@ -46,8 +51,8 @@ class AnvilResultListener : Listener { @EventHandler(ignoreCancelled = true) fun anvilExtractionCheck(event: InventoryClickEvent) { val player = event.whoClicked as? Player ?: return - if (!player.hasPermission(CustomAnvil.affectedByPluginPermission)) return val inventory = event.inventory as? AnvilInventory ?: return + val view = event.view if (event.rawSlot != ANVIL_OUTPUT_SLOT) { return @@ -56,84 +61,116 @@ class AnvilResultListener : Listener { // Test if the event should bypass custom anvil. if (DependencyManager.tryClickAnvilResultBypass(event, inventory)) return + if (!player.hasPermission(CustomAnvil.affectedByPluginPermission)) return + val output = inventory.getItem(ANVIL_OUTPUT_SLOT) ?: return val leftItem = inventory.getItem(ANVIL_INPUT_LEFT) ?: return val rightItem = inventory.getItem(ANVIL_INPUT_RIGHT) + // Deny by default. allow if working + event.result = Event.Result.DENY if (GameMode.CREATIVE != player.gameMode && inventory.repairCost >= inventory.maximumRepairCost) { - event.result = Event.Result.DENY return } // Test custom recipe - val recipe = CustomRecipeUtil.getCustomRecipe(leftItem, rightItem) - if (recipe != null) { - event.result = Event.Result.ALLOW + val customRecipeResult = AnvilMergeLogic.testCustomRecipe(view, inventory, player, leftItem, rightItem) + if (!customRecipeResult.isEmpty()) { onCustomCraft( - event, recipe, player, - leftItem, rightItem, output, inventory + event, player, inventory, + leftItem, rightItem, customRecipeResult ) return } // Do not continue if there was no change if ((output == inventory.getItem(ANVIL_INPUT_LEFT))) { - event.result = Event.Result.DENY return } // Rename if (rightItem == null) { - event.result = Event.Result.ALLOW + val result = AnvilMergeLogic.doRenaming(view, inventory, player, leftItem) + if (result.isEmpty()) return + + extractAnvilResult( + event, player, inventory, + null, 0, + null, 0, + result + ) return } // Merge val canMerge = leftItem.canMergeWith(rightItem) if (canMerge) { - event.result = Event.Result.ALLOW + val result = AnvilMergeLogic.doMerge(view, inventory, player, leftItem, rightItem) + + extractAnvilResult( + event, player, inventory, + null, 0, + null, 0, + result + ) return } // Unit repair - val unitRepairResult = leftItem.getRepair(rightItem) - if (unitRepairResult != null) { + val unitRepairResult = AnvilMergeLogic.testUnitRepair( + view, inventory, player, + leftItem, rightItem + ) + if (!unitRepairResult.isEmpty()) { onUnitRepairExtract( - leftItem, rightItem, output, - unitRepairResult, event, player, inventory + rightItem, event, player, inventory, + unitRepairResult ) return } // For lore edit - if (handleBookLoreEdit(event, inventory, player, leftItem, rightItem, output)) { - return - } else if (handlePaperLoreEdit(event, inventory, player, leftItem, rightItem, output)) { + val loreResult = AnvilMergeLogic.testLoreEdit(player, leftItem, rightItem) + if (!loreResult.isEmpty()) { + if (loreResult.type.isBook) + handleBookLoreEdit(event, inventory, player, leftItem, rightItem, loreResult) + else + handlePaperLoreEdit(event, inventory, player, leftItem, rightItem, loreResult) return } - - // Else there was no working situation somehow so we deny - event.result = Event.Result.DENY } private fun onCustomCraft( event: InventoryClickEvent, - recipe: AnvilCustomRecipe, player: Player, + inventory: AnvilInventory, leftItem: ItemStack, rightItem: ItemStack?, - output: ItemStack, - inventory: AnvilInventory + result: CustomCraftResult, ) { - event.result = Event.Result.DENY + val recipe = result.recipe!! + val rawCost = result.customCraftCost.rawCost + val finalCost = + if (recipe.removeExactLinearXp) rawCost + else AnvilXpUtil.calculateLevelForXp(rawCost) - if (recipe.leftItem == null) return // in case it changed + CustomAnvil.log( + "gamemode: ${player.gameMode != GameMode.CREATIVE}, " + + "cost: $finalCost, level: ${player.level}, " + + "result: ${player.totalExperience < finalCost} ${player.level < finalCost}" + ) - val amount = CustomRecipeUtil.getCustomRecipeAmount(recipe, leftItem, rightItem) - val xpCost = amount * recipe.xpCostPerCraft - - CustomAnvil.log("gamemode: ${player.gameMode != GameMode.CREATIVE}, cost: $xpCost, level: ${player.level}, result: ${player.level < xpCost}") - if ((player.gameMode != GameMode.CREATIVE) && (player.level < xpCost)) return + if (player.gameMode != GameMode.CREATIVE) { + if (ConfigOptions.shouldUseMoney(player)) { + result.cost.isMonetary = true + if (!EconomyManager.economy!!.has(player, result.cost.asMonetaryCost())) return + } else if (recipe.removeExactLinearXp) { + val levelXp = AnvilXpUtil.calculateXpForLevel(player.level) + val delta = AnvilXpUtil.calculateXpForLevel(player.level + 1) - levelXp + val totalXp = levelXp + player.exp * delta + if (totalXp < finalCost) return + } else if (player.level < finalCost) return + } // We give the item manually // But first we check if we should give the item @@ -142,23 +179,33 @@ class AnvilResultListener : Listener { // Handle not creative middle click... if (event.click != ClickType.MIDDLE && - !handleCustomCraftClick(event, recipe, inventory, player, leftItem, rightItem, amount, xpCost) + !handleCustomCraftClick( + event, + inventory, + player, + leftItem, + rightItem, + result + ) ) return // Finally, we add the item to the player if (slotDestination.type == SlotType.CURSOR) { - player.setItemOnCursor(output) + player.setItemOnCursor(result.item) } else {// We assume SlotType == SlotType.INVENTORY - player.inventory.setItem(slotDestination.slot, output) + player.inventory.setItem(slotDestination.slot, result.item) } } private fun handleCustomCraftClick( - event: InventoryClickEvent, recipe: AnvilCustomRecipe, + event: InventoryClickEvent, inventory: AnvilInventory, player: Player, leftItem: ItemStack, rightItem: ItemStack?, - amount: Int, xpCost: Int + result: CustomCraftResult ): Boolean { + val amount = result.amount + val recipe = result.recipe!! + // We remove what should be removed if (rightItem != null) { if (recipe.rightItem == null) return false// in case it changed @@ -170,9 +217,7 @@ class AnvilResultListener : Listener { leftItem.amount -= amount * recipe.leftItem!!.amount inventory.setItem(ANVIL_INPUT_LEFT, leftItem) - if (player.gameMode != GameMode.CREATIVE) { - player.level -= xpCost - } + removeCustomCraftCost(player, result) // Then we try to find the new values for the anvil val newAmount = CustomRecipeUtil.getCustomRecipeAmount(recipe, leftItem, rightItem) @@ -196,6 +241,53 @@ class AnvilResultListener : Listener { return true } + private fun removeCustomCraftCost(player: Player, result: CustomCraftResult) { + if (player.gameMode == GameMode.CREATIVE) return + + val rawCost = result.customCraftCost.rawCost + if (result.cost.isMonetary) { + EconomyManager.economy!!.remove(player, result.cost.asMonetaryCost()) + return + } + + if (result.recipe!!.removeExactLinearXp) { + val levelXp = AnvilXpUtil.calculateXpForLevel(player.level) + val delta = AnvilXpUtil.calculateXpForLevel(player.level + 1) - levelXp + var totalXp = levelXp + player.exp * delta + totalXp -= rawCost + + val newLevel = AnvilXpUtil.calculateLevelForXp(totalXp.toInt()) + + val newLevelXp = AnvilXpUtil.calculateXpForLevel(newLevel) + val newDelta = AnvilXpUtil.calculateXpForLevel(newLevel + 1) - newLevelXp + val xp = (totalXp - newLevelXp) / newDelta + + player.level = newLevel + player.exp = xp / newDelta + } else { + player.level -= AnvilXpUtil.calculateLevelForXp(rawCost) + } + + } + + private fun tryRemoveCost(player: Player, result: AnvilResult): Boolean { + if (player.gameMode == GameMode.CREATIVE) return true + + val cost = result.cost + if (cost.isMonetary) { + val result = EconomyManager.economy!!.remove(player, cost.asMonetaryCost()) + if (!result) return false + } else { + val xpCost = cost.filteredXpCost() + if (xpCost > AnvilXpUtil.maximumXpCost(result.ignoreXpRules)) return false + if (player.level < xpCost) return false + + player.level -= xpCost + } + + return true + } + private fun extractAnvilResult( event: InventoryClickEvent, player: Player, @@ -204,15 +296,17 @@ class AnvilResultListener : Listener { leftRemoveCount: Int, rightItem: ItemStack?, rightRemoveCount: Int, - output: ItemStack, - repairCost: Int, + result: AnvilResult ): Boolean { + if (result.isEmpty()) return false + // To avoid vanilla, we cancel the event event.result = Event.Result.DENY event.isCancelled = true + val cost = result.cost - // Assumed if player do not have enough xp then it returned MIN_VALUE - if (repairCost == Int.MIN_VALUE) return false + processCost(inventory, player, cost) + if (!cost.valid && player.gameMode != GameMode.CREATIVE) return false // Where should we get the item val slotDestination = getActionSlot(event, player) @@ -220,6 +314,8 @@ class AnvilResultListener : Listener { // If not creative middle click... if (event.click != ClickType.MIDDLE) { + if (!tryRemoveCost(player, result)) return false + // We remove what should be removed if (leftItem != null) leftItem.amount -= leftRemoveCount inventory.setItem(ANVIL_INPUT_LEFT, leftItem) @@ -228,99 +324,58 @@ class AnvilResultListener : Listener { inventory.setItem(ANVIL_INPUT_RIGHT, rightItem) inventory.setItem(ANVIL_OUTPUT_SLOT, null) - player.level -= repairCost + } // Finally, we add the item to the player if (SlotType.CURSOR == slotDestination.type) { - player.setItemOnCursor(output) + player.setItemOnCursor(result.item) } else {// We assume SlotType == SlotType.INVENTORY - player.inventory.setItem(slotDestination.slot, output) + player.inventory.setItem(slotDestination.slot, result.item) } // TODO probably anvil damage & sound here ?? return true } - private fun onUnitRepairExtract( - leftItem: ItemStack, - rightItem: ItemStack, - output: ItemStack, - unitRepairResult: Double, - event: InventoryClickEvent, - player: Player, - inventory: AnvilInventory - ) { - val resultCopy = leftItem.clone() - val resultAmount = resultCopy.unitRepair( - rightItem.amount, unitRepairResult - ) - - // Get repair cost - val repairCost = getUnitRepairCost(inventory, player, leftItem, output, resultCopy, resultAmount) - - // And then we give the item manually - extractAnvilResult( - event, player, inventory, - null, 0, - rightItem, resultAmount, - resultCopy, repairCost - ) - } - - private fun getUnitRepairCost( - inventory: AnvilInventory, player: Player, - leftItem: ItemStack, output: ItemStack, - resultCopy: ItemStack, resultAmount: Int - ): Int { - if (player.gameMode == GameMode.CREATIVE) return 0 - - var repairCost = 0 - // Get repairCost - leftItem.itemMeta?.let { leftMeta -> - val leftName = leftMeta.displayName - output.itemMeta?.let { - // Rename cost - if (!leftName.contentEquals(it.displayName)) { - repairCost += ConfigOptions.itemRenameCost - - // Color cost - if (it.displayName.contains('§')) { - repairCost += ConfigOptions.useOfColorCost - } - } - } - } - - repairCost += AnvilXpUtil.calculatePenalty(leftItem, null, resultCopy, AnvilUseType.UNIT_REPAIR) - repairCost += resultAmount * ConfigOptions.unitRepairCost + private fun processCost(inventory: AnvilInventory, player: Player, cost: AnvilCost) { + var sum = cost.repair if ( !ConfigOptions.doRemoveCostLimit && ConfigOptions.doCapCost ) { - repairCost = min(repairCost, ConfigOptions.maxAnvilCost) + val final = min(sum, ConfigOptions.maxAnvilCost) + cost.generic += (final - sum) + + sum = final } - if ((inventory.maximumRepairCost <= repairCost) - || (player.level < repairCost) - ) return Int.MIN_VALUE - - return repairCost + if (ConfigOptions.shouldUseMoney(player)) { + cost.isMonetary = true + if (!EconomyManager.economy!!.has(player, cost.asMonetaryCost())) + cost.valid = false + } else { + if ((inventory.maximumRepairCost <= sum) + || (player.level < sum) + ) cost.valid = false + } } - private fun getFromLoreEditXpCost( - xpCost: AtomicInteger, + private fun onUnitRepairExtract( + rightItem: ItemStack, + event: InventoryClickEvent, player: Player, inventory: AnvilInventory, - ): Int { - if (GameMode.CREATIVE == player.gameMode) return 0 - - val repairCost = xpCost.get() - return if ((inventory.maximumRepairCost <= repairCost) - || (player.level < repairCost) - ) Int.MIN_VALUE - else repairCost + result: UnitRepairResult, + ) { + // We give the item manually + extractAnvilResult( + event, player, inventory, + null, 0, + rightItem, result.repairAmount, + result + ) } private fun handleBookLoreEdit( @@ -329,70 +384,84 @@ class AnvilResultListener : Listener { player: Player, leftItem: ItemStack, rightItem: ItemStack, - output: ItemStack, - ): Boolean { - if (Material.WRITABLE_BOOK != rightItem.type) return false - val bookMeta = rightItem.itemMeta as BookMeta? ?: return false + result: LoreEditResult + ) { + if (result.type.isAppend) + handleBookLoreAppend(event, inventory, player, rightItem, result) + else + handleBookLoreRemove(event, inventory, player, leftItem, rightItem, result) + } - val editType = AnvilLoreEditUtil.bookLoreEditIsAppend(leftItem, rightItem) ?: return false + private fun handleBookLoreAppend( + event: InventoryClickEvent, + inventory: AnvilInventory, + player: Player, + rightItem: ItemStack, + result: LoreEditResult + ) { + val bookMeta = rightItem.itemMeta as BookMeta? ?: return - val xpCost = AtomicInteger() - if (editType) { - if (output != AnvilLoreEditUtil.handleLoreAppendByBook(player, leftItem, bookMeta, xpCost)) return false - - // Remove pages to book - val clearedBook: ItemStack? - if (LoreEditType.APPEND_BOOK.doConsume) { - clearedBook = null - } else { - clearedBook = rightItem.clone() - bookMeta.pages = Collections.emptyList() - clearedBook.itemMeta = bookMeta - } - - return extractAnvilResult( - event, player, inventory, - null, 0, - clearedBook, 0, - output, getFromLoreEditXpCost(xpCost, player, inventory) - ) + // Remove pages to book + val clearedBook: ItemStack? + if (LoreEditType.APPEND_BOOK.doConsume) { + clearedBook = null } else { - if (output != AnvilLoreEditUtil.handleLoreRemoveByBook(player, leftItem, xpCost)) return false + clearedBook = rightItem.clone() + bookMeta.pages = Collections.emptyList() + clearedBook.itemMeta = bookMeta + } - // fill book meta - val meta = leftItem.itemMeta - if (meta == null || !meta.hasLore()) return false - val lore = DependencyManager.stripLore(leftItem) - if (lore.isEmpty()) return false + extractAnvilResult( + event, player, inventory, + null, 0, + clearedBook, 0, + result + ) + } - val rightCopy: ItemStack? - if (LoreEditType.REMOVE_BOOK.doConsume) { - rightCopy = null - } else { - // Uncolor the page - AnvilLoreEditUtil.uncolorLines(player, lore, LoreEditType.REMOVE_BOOK) + private fun handleBookLoreRemove( + event: InventoryClickEvent, + inventory: AnvilInventory, + player: Player, + leftItem: ItemStack, + rightItem: ItemStack, + result: LoreEditResult + ) { + val bookMeta = rightItem.itemMeta as BookMeta? ?: return - val bookPage = StringBuilder() - lore.forEach { - if (bookPage.isNotEmpty()) bookPage.append('\n') - bookPage.append(it) - } + // fill book meta + val lore = DependencyManager.stripLore(leftItem) + if (lore.isEmpty()) return - val resultPage = bookPage.toString() - //TODO maybe check page size ? bc it may be too big ??? + val rightCopy: ItemStack? + if (LoreEditType.REMOVE_BOOK.doConsume) { + rightCopy = null + } else { + // Uncolor the page + AnvilLoreEditUtil.uncolorLines(player, lore, LoreEditType.REMOVE_BOOK) - rightCopy = rightItem.clone() - bookMeta.setPages(resultPage) - rightCopy.itemMeta = bookMeta + val bookPage = StringBuilder() + lore.forEach { + if (bookPage.isNotEmpty()) bookPage.append('\n') + if (it == null) return@forEach + + bookPage.append(MiniMessageUtil.plain_text_mm.serialize(it)) } - return extractAnvilResult( - event, player, inventory, - null, 0, - rightCopy, 0, - output, getFromLoreEditXpCost(xpCost, player, inventory) - ) + val resultPage = bookPage.toString() + //TODO maybe check page size ? bc it may be too big ??? + + rightCopy = rightItem.clone() + bookMeta.setPages(resultPage) + rightCopy.itemMeta = bookMeta } + + extractAnvilResult( + event, player, inventory, + null, 0, + rightCopy, 0, + result + ) } private fun handlePaperLoreEdit( @@ -401,91 +470,106 @@ class AnvilResultListener : Listener { player: Player, leftItem: ItemStack, rightItem: ItemStack, - output: ItemStack, - ): Boolean { - if (Material.PAPER != rightItem.type) return false - val paperMeta = rightItem.itemMeta ?: return false + result: LoreEditResult + ) { + if (result.type.isAppend) + handlePaperLoreAppend(event, inventory, player, rightItem, result) + else + handlePaperLoreRemove(event, inventory, player, leftItem, rightItem, result) + } - val editType = AnvilLoreEditUtil.paperLoreEditIsAppend(leftItem, rightItem) ?: return false + private fun handlePaperLoreAppend( + event: InventoryClickEvent, + inventory: AnvilInventory, + player: Player, + rightItem: ItemStack, + result: LoreEditResult + ) { + val paperMeta = rightItem.itemMeta ?: return - val xpCost = AtomicInteger() - if (editType) { - if (output != AnvilLoreEditUtil.handleLoreAppendByPaper(player, leftItem, rightItem, xpCost)) return false - val paperCopy: ItemStack? - if (LoreEditType.APPEND_PAPER.doConsume) { - paperCopy = null - } else { - // Remove custom name to paper - paperCopy = rightItem.clone() - paperCopy.amount = 1 - paperMeta.setDisplayName(null) - paperCopy.itemMeta = paperMeta - } - - return if (rightItem.amount > 1) { - extractAnvilResult( - event, player, inventory, - paperCopy, 0, - rightItem, 1, - output, getFromLoreEditXpCost(xpCost, player, inventory) - ) - } else { - extractAnvilResult( - event, player, inventory, - null, 0, - paperCopy, 0, - output, getFromLoreEditXpCost(xpCost, player, inventory) - ) - } + val paperCopy: ItemStack? + if (LoreEditType.APPEND_PAPER.doConsume) { + paperCopy = null } else { - if (output != AnvilLoreEditUtil.handleLoreRemoveByPaper(player, leftItem, xpCost)) return false + // Remove custom name to paper + paperCopy = rightItem.clone() + paperCopy.amount = 1 + paperMeta.setComponentDisplayName(null) - val leftMeta = leftItem.itemMeta - if (leftMeta == null || !leftMeta.hasLore()) return false - val lore = DependencyManager.stripLore(leftItem) - if (lore.isEmpty()) return false + // Remove pcd name + AnvilMergeLogic.processPCD(paperMeta, player, null) - // Create result item - val rightClone: ItemStack? - if (LoreEditType.REMOVE_PAPER.doConsume) { - rightClone = null - } else { - val removeEnd = LoreEditConfigUtil.paperLoreOrderIsEnd - var line = if (removeEnd) lore[lore.size - 1] - else lore[0] - - // Overkill but uncolor the line - val tempList = ArrayList(1) - tempList.add(line) - AnvilLoreEditUtil.uncolorLines(player, tempList, LoreEditType.REMOVE_PAPER) - line = tempList[0] - - rightClone = rightItem.clone() - rightClone.amount = 1 - - val resultMeta = rightClone.itemMeta ?: return false - resultMeta.setDisplayName(line) - rightClone.itemMeta = resultMeta - } - - return if (rightItem.amount > 1) { - extractAnvilResult( - event, player, inventory, - rightClone, 0, - rightItem, 1, - output, getFromLoreEditXpCost(xpCost, player, inventory) - ) - } else { - extractAnvilResult( - event, player, inventory, - null, 0, - rightClone, 0, - output, getFromLoreEditXpCost(xpCost, player, inventory) - ) - } + paperCopy.itemMeta = paperMeta } + if (rightItem.amount > 1) { + extractAnvilResult( + event, player, inventory, + paperCopy, 0, + rightItem, 1, + result + ) + } else { + extractAnvilResult( + event, player, inventory, + null, 0, + paperCopy, 0, + result + ) + } + } + + private fun handlePaperLoreRemove( + event: InventoryClickEvent, + inventory: AnvilInventory, + player: Player, + leftItem: ItemStack, + rightItem: ItemStack, + result: LoreEditResult + ) { + val leftMeta = leftItem.itemMeta + if (leftMeta == null || !leftMeta.hasLore()) return + + val lore = DependencyManager.stripLore(leftItem) + if (lore.isEmpty()) return + + // Create result item + val rightClone: ItemStack? + if (LoreEditType.REMOVE_PAPER.doConsume) { + rightClone = null + } else { + val removeEnd = LoreEditConfigUtil.paperLoreOrderIsEnd + val line = if (removeEnd) lore[lore.size - 1] + else lore[0] + + // uncolor the line + val ref = AtomicReference(line) + AnvilLoreEditUtil.uncolorLine(player, ref, LoreEditType.REMOVE_PAPER) + + rightClone = rightItem.clone() + rightClone.amount = 1 + + val resultMeta = rightClone.itemMeta ?: return + resultMeta.setComponentDisplayName(ref.get()) + rightClone.itemMeta = resultMeta + } + + if (rightItem.amount > 1) { + extractAnvilResult( + event, player, inventory, + rightClone, 0, + rightItem, 1, + result + ) + } else { + extractAnvilResult( + event, player, inventory, + null, 0, + rightClone, 0, + result + ) + } } /** diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt index 5708126..0217983 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt @@ -3,26 +3,29 @@ package xyz.alexcrea.cuanvil.listener import com.github.stefvanschie.inventoryframework.util.InventoryViewUtil import io.delilaheve.CustomAnvil import io.delilaheve.util.ConfigOptions -import io.delilaheve.util.EnchantmentUtil.combineWith import io.delilaheve.util.ItemUtil.canMergeWith -import io.delilaheve.util.ItemUtil.findEnchantments -import io.delilaheve.util.ItemUtil.isEnchantedBook -import io.delilaheve.util.ItemUtil.repairFrom -import io.delilaheve.util.ItemUtil.setEnchantmentsUnsafe -import io.delilaheve.util.ItemUtil.unitRepair -import org.bukkit.ChatColor -import org.bukkit.Material import org.bukkit.entity.HumanEntity +import org.bukkit.entity.Player import org.bukkit.event.EventHandler import org.bukkit.event.EventPriority import org.bukkit.event.Listener import org.bukkit.event.inventory.PrepareAnvilEvent import org.bukkit.inventory.AnvilInventory +import org.bukkit.inventory.InventoryView import org.bukkit.inventory.ItemStack +import org.bukkit.inventory.meta.EnchantmentStorageMeta +import org.bukkit.inventory.meta.ItemMeta +import xyz.alexcrea.cuanvil.anvil.AnvilCost +import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.AnvilResult +import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.doMerge +import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.doRenaming +import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.testCustomRecipe +import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.testLoreEdit +import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.testUnitRepair import xyz.alexcrea.cuanvil.dependency.DependencyManager -import xyz.alexcrea.cuanvil.util.* -import xyz.alexcrea.cuanvil.util.UnitRepairUtil.getRepair -import java.util.concurrent.atomic.AtomicInteger +import xyz.alexcrea.cuanvil.util.MaterialUtil.isAir +import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil +import xyz.alexcrea.cuanvil.util.dialog.AnvilRenameDialogUtil /** * Listener for anvil events @@ -35,6 +38,8 @@ class PrepareAnvilListener : Listener { const val ANVIL_INPUT_LEFT = 0 const val ANVIL_INPUT_RIGHT = 1 const val ANVIL_OUTPUT_SLOT = 2 + + var IS_EMPTY_TEST = false } /** @@ -42,213 +47,135 @@ class PrepareAnvilListener : Listener { */ @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) fun anvilCombineCheck(event: PrepareAnvilEvent) { - // Should find player - val player: HumanEntity = InventoryViewUtil.getInstance().getPlayer(event.view) + val view = event.view + val inventory = event.inventory + + val player = InventoryViewUtil.getInstance().getPlayer(view) + if(player !is Player) return + + tryRenameDialog(player, event) + + // Test if custom anvil is bypassed before immutability test + if (DependencyManager.earlyTryEventPreAnvilBypass(event, player)) { + // even if we got bypassed we still want to set price + AnvilXpUtil.setAnvilInvCost(inventory, view, player, AnvilCost(event.inventory.repairCost)) + return + } + + val first = inventory.getItem(ANVIL_INPUT_LEFT) + val second = inventory.getItem(ANVIL_INPUT_RIGHT) + + if(IS_EMPTY_TEST) { + IS_EMPTY_TEST = false + applyResult(event, player, AnvilResult.EMPTY) + return + } + + if (ConfigOptions.verboseDebugLog) { + CustomAnvil.verboseLog("Testing items:") + CustomAnvil.verboseLog("first: $first") + CustomAnvil.verboseLog("second: $second") + } + if (isImmutable(first) || isImmutable(second)) { + CustomAnvil.verboseLog("Skipping anvil process as one of the two item is immutable") + + applyResult(event, player, AnvilResult.EMPTY) + return + } // Test if the event should bypass custom anvil. - if (DependencyManager.tryEventPreAnvilBypass(event, player)) return - - val inventory = event.inventory - val first = inventory.getItem(ANVIL_INPUT_LEFT) ?: return - val second = inventory.getItem(ANVIL_INPUT_RIGHT) + if (DependencyManager.tryEventPreAnvilBypass(event, player)) { + // even if we got bypassed we still want to set price + AnvilXpUtil.setAnvilInvCost(inventory, view, player, AnvilCost(event.inventory.repairCost)) + return + } if (!player.hasPermission(CustomAnvil.affectedByPluginPermission)) return + val result = getResult(view, inventory, player, first, second) + applyResult(event, player, result) + } + + fun getResult( + view: InventoryView, //TODO use anvil view + inventory: AnvilInventory, + player: Player, + first: ItemStack?, second: ItemStack?) : AnvilResult + { + if(first == null) + return AnvilResult.EMPTY + // Test custom recipe - if(testCustomRecipe(event, inventory, player, first, second)) return + var result: AnvilResult = testCustomRecipe(view, inventory, player, first, second) + if (!result.isEmpty()) + return result // Test rename lonely item - if(second == null) { - doRenaming(event, inventory, player, first) - return - } + val shouldTryRename = second.isAir + CustomAnvil.verboseLog("checking air in main logic: $shouldTryRename") + if (shouldTryRename) + return doRenaming(view, inventory, player, first) // Test for merge - if (first.canMergeWith(second)) { - doMerge(event, inventory, player, first, second) - return - } + if (first.canMergeWith(second!!)) + return doMerge(view, inventory, player, first, second) // Test for unit repair - if(testUnitRepair(event, inventory, player, first, second)) return + result = testUnitRepair(view, inventory, player, first, second) + if (!result.isEmpty()) + return result // Test for lore edit - if(testLoreEdit(event, inventory, player, first, second)) return - - CustomAnvil.log("no anvil fuse type found") - event.result = null + result = testLoreEdit(player, first, second) + if (!result.isEmpty()) + return result + return AnvilResult.EMPTY } - // return true if a custom recipe exist with these ingredients - private fun testCustomRecipe(event: PrepareAnvilEvent, inventory: AnvilInventory, - player: HumanEntity, - first: ItemStack, second: ItemStack?): Boolean { - val recipe = CustomRecipeUtil.getCustomRecipe(first, second) - CustomAnvil.verboseLog("custom recipe not null? ${recipe != null}") - if(recipe == null) return false + private fun tryRenameDialog( + player: HumanEntity, + event: PrepareAnvilEvent + ) { + if(!ConfigOptions.canUseDialogRename(player)) return - val amount = CustomRecipeUtil.getCustomRecipeAmount(recipe, first, second) - - val resultItem: ItemStack = recipe.resultItem!!.clone() - resultItem.amount *= amount - - event.result = resultItem - if(DependencyManager.tryTreatAnvilResult(event, resultItem)) return true - - // Maybe add an option on custom craft to ignore/not ignore penalty ?? - var xpCost = recipe.xpCostPerCraft * amount - xpCost += AnvilXpUtil.calculatePenalty(first, null, resultItem, AnvilUseType.CUSTOM_CRAFT) - - AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, xpCost, true) - - return true + AnvilRenameDialogUtil.anvilRenameDialog.tryShowDialog(player, event) } - private fun doRenaming(event: PrepareAnvilEvent, inventory: AnvilInventory, - player: HumanEntity, first: ItemStack) { - val resultItem = first.clone() - var anvilCost = handleRename(resultItem, inventory, player) + private fun isImmutable(item: ItemStack?): Boolean { + if (item.isAir) return false - // Test/stop if nothing changed. - if (first == resultItem) { - CustomAnvil.log("no right item, But input is same as output") - event.result = null + val meta = item!!.itemMeta + return meta != null && + (hasImmutableEnchants(meta) || hasImmutableStoredEnchants(meta)) + } + + private fun hasImmutableEnchants(meta: ItemMeta): Boolean { + if (!meta.hasEnchants()) return false + + for (enchant in meta.enchants.keys) { + if (ConfigOptions.isImmutable(enchant.key)) return true + } + return false + } + + private fun hasImmutableStoredEnchants(meta: ItemMeta): Boolean { + if (meta !is EnchantmentStorageMeta || !meta.hasStoredEnchants()) return false + + for (enchant in meta.storedEnchants.keys) { + if (ConfigOptions.isImmutable(enchant.key)) return true + } + return false + } + + private fun applyResult(event: PrepareAnvilEvent, player: Player, result: AnvilResult) { + event.result = result.item + + if(result.item == null) { + AnvilXpUtil.onNoResult(player, event.view) return } - - event.result = resultItem - if(DependencyManager.tryTreatAnvilResult(event, resultItem)) return - - anvilCost += AnvilXpUtil.calculatePenalty(first, null, resultItem, AnvilUseType.RENAME_ONLY) - - AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, anvilCost) + AnvilXpUtil.setAnvilInvCost(event.inventory, event.view, player, result.cost, result.ignoreXpRules) } - private fun handleRename(resultItem: ItemStack, inventory: AnvilInventory, player: HumanEntity): Int { - // Can be null - var inventoryName = ChatColor.stripColor(inventory.renameText) - - var sumCost = 0 - var useColor = false - if(ConfigOptions.renameColorPossible && inventoryName != null){ - val resultString = StringBuilder(inventoryName) - - useColor = AnvilColorUtil.handleColor(resultString, player, - ConfigOptions.permissionNeededForColor, - ConfigOptions.allowColorCode, ConfigOptions.allowHexadecimalColor, - AnvilColorUtil.ColorUseType.RENAME) - - if(useColor) { - inventoryName = resultString.toString() - - sumCost+= ConfigOptions.useOfColorCost - } - } - - // Rename item and add renaming cost - resultItem.itemMeta?.let { - val hasDisplayName = it.hasDisplayName() - val displayName = if (!hasDisplayName) null - else if (useColor) it.displayName - else ChatColor.stripColor(it.displayName) - - if (!displayName.contentEquals(inventoryName)) { - it.setDisplayName(inventoryName) - resultItem.itemMeta = it - - sumCost += ConfigOptions.itemRenameCost - } - - return sumCost - } - return 0 - } - - private fun doMerge(event: PrepareAnvilEvent, inventory: AnvilInventory, - player: HumanEntity, - first: ItemStack, second: ItemStack) { - val newEnchants = first.findEnchantments() - .combineWith(second.findEnchantments(), first, player) - val resultItem = first.clone() - resultItem.setEnchantmentsUnsafe(newEnchants) - - // Calculate enchantment cost - var anvilCost = AnvilXpUtil.getRightValues(second, resultItem) - // Calculate repair cost - if (!first.isEnchantedBook() && !second.isEnchantedBook()) { - // we only need to be concerned with repair when neither item is a book - val repaired = resultItem.repairFrom(first, second) - anvilCost += if (repaired) ConfigOptions.itemRepairCost else 0 - } - - // Test/stop if nothing changed. - if (first == resultItem) { - CustomAnvil.log("Mergable with second, But input is same as output") - event.result = null - return - } - // As calculatePenalty edit result, we need to calculate penalty after checking equality - anvilCost += AnvilXpUtil.calculatePenalty(first, second, resultItem, AnvilUseType.MERGE) - // Calculate rename cost - anvilCost += handleRename(resultItem, inventory, player) - - // Finally, we set result - event.result = resultItem - if(DependencyManager.tryTreatAnvilResult(event, resultItem)) return - - AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, anvilCost) - } - - // return true if there is a valid unit repair with these ingredients - private fun testUnitRepair(event: PrepareAnvilEvent, inventory: AnvilInventory, player: HumanEntity, - first: ItemStack, second: ItemStack): Boolean { - val unitRepairAmount = first.getRepair(second) ?: return false - - val resultItem = first.clone() - var anvilCost = handleRename(resultItem, inventory, player) - - val repairAmount = resultItem.unitRepair(second.amount, unitRepairAmount) - if (repairAmount > 0) { - anvilCost += repairAmount * ConfigOptions.unitRepairCost - } - // We do not care about right item penalty for unit repair - anvilCost += AnvilXpUtil.calculatePenalty(first, null, resultItem, AnvilUseType.UNIT_REPAIR) - - // Test/stop if nothing changed. - if (first == resultItem) { - CustomAnvil.log("unit repair, But input is same as output") - event.result = null - return true - } - event.result = resultItem - if(DependencyManager.tryTreatAnvilResult(event, resultItem)) return true - - AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, anvilCost) - return true - } - - private fun testLoreEdit(event: PrepareAnvilEvent, inventory: AnvilInventory, player: HumanEntity, - first: ItemStack, second: ItemStack): Boolean { - val type = second.type - var result: ItemStack? = null - - val xpCost = AtomicInteger() - if(Material.WRITABLE_BOOK == type) { - result = AnvilLoreEditUtil.tryLoreEditByBook(player, first, second, xpCost) - } - else if(Material.PAPER == type) { - result = AnvilLoreEditUtil.tryLoreEditByPaper(player, first, second, xpCost) - } - - if(result == null || first == result) { - CustomAnvil.log("lore edit, But input is same as output") - event.result = null - return false - } - - event.result = result - AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, xpCost.get()) - return true - } } \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/recipe/AnvilCustomRecipe.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/recipe/AnvilCustomRecipe.kt index 114dbe3..fd079c3 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/recipe/AnvilCustomRecipe.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/recipe/AnvilCustomRecipe.kt @@ -3,8 +3,11 @@ package xyz.alexcrea.cuanvil.recipe import io.delilaheve.CustomAnvil import org.bukkit.configuration.ConfigurationSection import org.bukkit.inventory.ItemStack +import xyz.alexcrea.cuanvil.anvil.AnvilUseType import xyz.alexcrea.cuanvil.config.ConfigHolder import xyz.alexcrea.cuanvil.gui.util.GuiSharedConstant +import xyz.alexcrea.cuanvil.util.MaterialUtil.isAir +import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil class AnvilCustomRecipe( val name: String, @@ -12,7 +15,10 @@ class AnvilCustomRecipe( //var exactLeft: Boolean, //var exactRight: Boolean, - var xpCostPerCraft: Int, + var levelCostPerCraft: Int, + + var XpCostPerCraft: Int, + var removeExactLinearXp: Boolean, var leftItem: ItemStack?, var rightItem: ItemStack?, @@ -25,7 +31,9 @@ class AnvilCustomRecipe( //const val EXACT_LEFT_CONFIG = "exact_left" //const val EXACT_RIGHT_CONFIG = "exact_right" - const val XP_COST_CONFIG = "xp_cost" + const val XP_LEVEL_COST_CONFIG = "xp_cost" + const val LINEAR_XP_COST_CONFIG = "linear_xp_cost" + const val REMOVE_EXACT_XP_CONFIG = "remove_exact_linear_xp" const val LEFT_ITEM_CONFIG = "left_item" const val RIGHT_ITEM_CONFIG = "right_item" @@ -36,7 +44,9 @@ class AnvilCustomRecipe( //val DEFAULT_EXACT_LEFT_CONFIG = true //val DEFAULT_EXACT_RIGHT_CONFIG = true - const val DEFAULT_XP_COST_CONFIG = 1 + const val DEFAULT_XP_LEVEL_COST_CONFIG = 1 + const val DEFAULT_LINEAR_XP_COST_CONFIG = 0 + const val DEFAULT_REMOVE_EXACT_XP_CONFIG = false val DEFAULT_LEFT_ITEM_CONFIG: ItemStack? = null val DEFAULT_RIGHT_ITEM_CONFIG: ItemStack? = null @@ -45,20 +55,24 @@ class AnvilCustomRecipe( val XP_COST_CONFIG_RANGE = 0..255 fun getFromConfig(name: String, configSection: ConfigurationSection?): AnvilCustomRecipe? { - if(configSection == null) return null + if (configSection == null) return null return AnvilCustomRecipe( name, configSection.getBoolean(EXACT_COUNT_CONFIG, DEFAULT_EXACT_COUNT_CONFIG), //configSection.getBoolean(EXACT_LEFT_CONFIG, true), //configSection.getBoolean(EXACT_RIGHT_CONFIG, true), - configSection.getInt(XP_COST_CONFIG, DEFAULT_XP_COST_CONFIG), + configSection.getInt(XP_LEVEL_COST_CONFIG, DEFAULT_XP_LEVEL_COST_CONFIG), + configSection.getInt(LINEAR_XP_COST_CONFIG, DEFAULT_LINEAR_XP_COST_CONFIG), + configSection.getBoolean(REMOVE_EXACT_XP_CONFIG, DEFAULT_REMOVE_EXACT_XP_CONFIG), + configSection.getItemStack(LEFT_ITEM_CONFIG, DEFAULT_LEFT_ITEM_CONFIG), + configSection.getItemStack(RIGHT_ITEM_CONFIG, DEFAULT_RIGHT_ITEM_CONFIG), configSection.getItemStack(RESULT_ITEM_CONFIG, DEFAULT_RESULT_ITEM_CONFIG), - ) + ) } fun getFromConfig(name: String): AnvilCustomRecipe? { @@ -67,21 +81,21 @@ class AnvilCustomRecipe( } fun validate(): Boolean { - return (leftItem != null) && !(leftItem!!.type.isAir) && (leftItem!!.amount > 0) && - //(rightItem != null) && !(rightItem!!.type.isAir) && (rightItem!!.amount > 0) && - ((rightItem == null) || (!(rightItem!!.type.isAir) && (rightItem!!.amount > 0))) && - (resultItem != null) && !(resultItem!!.type.isAir) && (resultItem!!.amount > 0) - + return !leftItem.isAir && + (rightItem == null || !resultItem.isAir) && + !resultItem.isAir } - fun saveToFile(writeFile: Boolean, doBackup: Boolean){ + fun saveToFile(writeFile: Boolean, doBackup: Boolean) { val fileConfig = ConfigHolder.CUSTOM_RECIPE_HOLDER.config fileConfig["$name.$EXACT_COUNT_CONFIG"] = exactCount //fileConfig.set("$name.$EXACT_LEFT_CONFIG", exactLeft) //fileConfig.set("$name.$EXACT_RIGHT_CONFIG", exactRight) - fileConfig["$name.$XP_COST_CONFIG"] = xpCostPerCraft + fileConfig["$name.$XP_LEVEL_COST_CONFIG"] = levelCostPerCraft + fileConfig["$name.$LINEAR_XP_COST_CONFIG"] = XpCostPerCraft + fileConfig["$name.$REMOVE_EXACT_XP_CONFIG"] = removeExactLinearXp fileConfig["$name.$LEFT_ITEM_CONFIG"] = leftItem fileConfig["$name.$RIGHT_ITEM_CONFIG"] = rightItem @@ -94,19 +108,32 @@ class AnvilCustomRecipe( } @Deprecated("Should use saveToFile(Boolean, Boolean) instead") //TODO determine when an where to save/do backup and remove use of variable like TEMPORARY_DO_SAVE_TO_DISK_EVERY_CHANGE - fun saveToFile(){ - saveToFile(GuiSharedConstant.TEMPORARY_DO_SAVE_TO_DISK_EVERY_CHANGE, GuiSharedConstant.TEMPORARY_DO_BACKUP_EVERY_SAVE) + fun saveToFile() { + saveToFile( + GuiSharedConstant.TEMPORARY_DO_SAVE_TO_DISK_EVERY_CHANGE, + GuiSharedConstant.TEMPORARY_DO_BACKUP_EVERY_SAVE + ) } - fun updateFromFile(){ + fun updateFromFile() { this.exactCount = ConfigHolder.CUSTOM_RECIPE_HOLDER.config.getBoolean( "$name.$EXACT_COUNT_CONFIG", DEFAULT_EXACT_COUNT_CONFIG ) - this.xpCostPerCraft = ConfigHolder.CUSTOM_RECIPE_HOLDER.config.getInt( - "$name.$XP_COST_CONFIG", - DEFAULT_XP_COST_CONFIG + this.levelCostPerCraft = ConfigHolder.CUSTOM_RECIPE_HOLDER.config.getInt( + "$name.$XP_LEVEL_COST_CONFIG", + DEFAULT_XP_LEVEL_COST_CONFIG + ) + + this.XpCostPerCraft = ConfigHolder.CUSTOM_RECIPE_HOLDER.config.getInt( + "$name.$LINEAR_XP_COST_CONFIG", + DEFAULT_LINEAR_XP_COST_CONFIG + ) + + this.removeExactLinearXp = ConfigHolder.CUSTOM_RECIPE_HOLDER.config.getBoolean( + "$name.$REMOVE_EXACT_XP_CONFIG", + DEFAULT_REMOVE_EXACT_XP_CONFIG ) // Update items @@ -134,31 +161,31 @@ class AnvilCustomRecipe( CustomAnvil.verboseLog("Testing $name $leftItem") // We assume this function can be call only if leftItem != null - // Test is valid - if(!validate()) return false + // Test if valid + if (!validate()) return false val leftSimilar = leftItem!!.isSimilar(item1) CustomAnvil.verboseLog("Validated test !") // test of left item - if(!leftSimilar) return false // Test similar - if(exactCount){ - if((leftItem!!.amount != item1.amount)) return false // test exact amount - }else if(item1.amount < leftItem!!.amount) return false // test if it has at least the amount we ask + if (!leftSimilar) return false // Test similar + if (exactCount) { + if ((leftItem!!.amount != item1.amount)) return false // test exact amount + } else if (item1.amount < leftItem!!.amount) return false // test if it has at least the amount we ask CustomAnvil.verboseLog("Left item passed !") // we don't know if right item can be - if(rightItem == null){ // null test - if(item2 != null) return false - }else { + if (rightItem == null) { // null test + if (item2 != null) return false + } else { val rightSimilar = rightItem!!.isSimilar(item2) CustomAnvil.verboseLog("Right similar: $rightSimilar") - if(!rightSimilar) return false // test if similar when not null + if (!rightSimilar) return false // test if similar when not null - if(exactCount) { + if (exactCount) { if (rightItem!!.amount != item2!!.amount) return false // test exact amount - }else if(item2!!.amount < rightItem!!.amount) return false // test if it has at least the amount we ask + } else if (item2!!.amount < rightItem!!.amount) return false // test if it has at least the amount we ask } CustomAnvil.verboseLog("Right item passed !") @@ -170,5 +197,18 @@ class AnvilCustomRecipe( return name } + fun determineCost(amount: Int, first: ItemStack, resultItem: ItemStack): Int { + // First we determine the non linear level cost + var levelCost = levelCostPerCraft * amount + // TODO Maybe add an option per custom craft to ignore/not ignore penalty ?? + levelCost += AnvilXpUtil.calculatePenalty(first, null, resultItem, AnvilUseType.CUSTOM_CRAFT) + + var xpCost = AnvilXpUtil.calculateXpForLevel(levelCost) + // Then we add the linear cost + xpCost += XpCostPerCraft * amount + + return xpCost + } + } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilColorUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilColorUtil.kt deleted file mode 100644 index 277a8d1..0000000 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilColorUtil.kt +++ /dev/null @@ -1,186 +0,0 @@ -package xyz.alexcrea.cuanvil.util - -import org.bukkit.permissions.Permissible -import java.util.regex.Matcher -import java.util.regex.Pattern - -object AnvilColorUtil { - private val HEX_PATTERN: Pattern = Pattern.compile("#[A-Fa-f0-9]{6}") // pattern to find hexadecimal string - private val TRANSFORMED_HEX_PATTERN = Pattern.compile("§x(§[0-9a-fA-F]){6}") // pattern to find minecraft hex string - - /** - * Color a stringbuilder object depending on allowed color type and player permissions on color use type - * @return if the stringbuilder was changed and color applied - */ - fun handleColor( - textToColor: StringBuilder, - player: Permissible, - usePermission: Boolean, - allowColorCode: Boolean, - allowHexadecimalColor: Boolean, - useType: ColorUseType - ): Boolean { - if (!allowColorCode && !allowHexadecimalColor) return false - - val canUseColorCode = - allowColorCode && (!usePermission || useType.colorCodePerm == null || player.hasPermission( - useType.colorCodePerm - )) - val canUseHexColor = - allowHexadecimalColor && (!usePermission || useType.hexColorPerm == null || player.hasPermission( - useType.hexColorPerm - )) - - if ((!canUseColorCode) && (!canUseHexColor)) return false - - var useColor = false - // Handle color code - if (canUseColorCode) { - var nbReplacement = replaceAll(textToColor, "&", "§", 2) - nbReplacement -= 2 * replaceAll(textToColor, "§§", "&", 2) - - if (nbReplacement > 0) useColor = true - } - - if (canUseHexColor) { - val nbReplacement = replaceHexToColor(textToColor, 7) - - if (nbReplacement > 0) useColor = true - } - - return useColor - } - - /** - * Revert a stringbuilder to a state where applying handleColor with the same options would give the same result - * @return if the stringbuilder was changed and color unapplied - */ - fun revertColor( - colorToText: StringBuilder, - player: Permissible, - usePermission: Boolean, - allowColorCode: Boolean, - allowHexadecimalColor: Boolean, - useType: ColorUseType - ): Boolean { - if (!allowColorCode && !allowHexadecimalColor) return false - - val canUseColorCode = - allowColorCode && (!usePermission || useType.colorCodePerm == null || player.hasPermission( - useType.colorCodePerm - )) - val canUseHexColor = - allowHexadecimalColor && (!usePermission || useType.hexColorPerm == null || player.hasPermission( - useType.hexColorPerm - )) - - if ((!canUseColorCode) && (!canUseHexColor)) return false - var hasReversed = false - - // Reverse hex pattern - if (canUseHexColor) { - val nbReplacement = replaceColorToHex(colorToText, 14) - - if (nbReplacement > 0) hasReversed = true - } - - if (canUseColorCode) { - replaceAll(colorToText, "&", "&&", 1) - val nbReplacement = replaceAll(colorToText, "§", "&", 2) - - if (nbReplacement > 0) hasReversed = true - } - - return hasReversed - } - - /** - * Replace every instance of "from" to "to". - * @param builder The builder to replace the string from. - * @param from The source that should be replaced. - * @param to The string that should replace. - * @param endOffset Amount of character that should be ignored at the end. - * @return The number of replacement was that was done. - */ - private fun replaceAll(builder: java.lang.StringBuilder, from: String, to: String, endOffset: Int): Int { - var index = builder.indexOf(from) - var numberOfChanges = 0 - - while (index != -1 && index < builder.length - endOffset) { - builder.replace(index, index + from.length, to) - index += to.length - index = builder.indexOf(from, index) - - numberOfChanges += 1 - } - - return numberOfChanges - } - - /** - * Replace every hex color formatted like #000000 to the minecraft format - * @param builder The builder to replace the hex color from. - * @param endOffset Amount of character that should be ignored at the end. - * @return The number of replacement was that was done. - */ - private fun replaceHexToColor(builder: StringBuilder, endOffset: Int): Int { - val matcher: Matcher = HEX_PATTERN.matcher(builder) - - var numberOfChanges = 0 - var startIndex = 0 - - while (matcher.find(startIndex)) { - startIndex = matcher.start() - if (startIndex >= builder.length - endOffset) break //HOW AND WHERE WOULD THIS HAPPEN ????? - - builder.replace(startIndex, startIndex + 1, "§x") - startIndex += 2 - for (i in 0..5) { - builder.insert(startIndex, '§') - startIndex += 2 - } - - numberOfChanges += 1 - } - - return numberOfChanges - } - - /** - * Replace every hex color from the minecraft format to a format like #000000 - * @param builder The builder to replace the minecraft hex color from. - * @param endOffset Amount of character that should be ignored at the end. - * @return The number of replacement was that was done. - */ - private fun replaceColorToHex(builder: StringBuilder, endOffset: Int): Int { - val matcher: Matcher = TRANSFORMED_HEX_PATTERN.matcher(builder) - - var numberOfChanges = 0 - var startIndex = 0 - - while (matcher.find(startIndex)) { - startIndex = matcher.start() - if (startIndex >= builder.length - endOffset) break //HOW AND WHERE WOULD THIS HAPPEN ????? - - builder.replace(startIndex, startIndex + 2, "#") - startIndex += 1 - for (i in 0..5) { - builder.deleteCharAt(startIndex) - startIndex += 1 - } - - numberOfChanges += 1 - } - - return numberOfChanges - } - - enum class ColorUseType( - val colorCodePerm: String?, - val hexColorPerm: String? - ) { - RENAME("ca.color.code", "ca.color.hex"), - LORE_EDIT(null, null) - } - -} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilLoreEditUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilLoreEditUtil.kt deleted file mode 100644 index e3b052a..0000000 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilLoreEditUtil.kt +++ /dev/null @@ -1,294 +0,0 @@ -package xyz.alexcrea.cuanvil.util - -import com.willfp.eco.util.toNiceString -import org.bukkit.entity.HumanEntity -import org.bukkit.inventory.ItemStack -import org.bukkit.inventory.meta.BookMeta -import org.bukkit.permissions.Permissible -import xyz.alexcrea.cuanvil.dependency.DependencyManager -import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil -import xyz.alexcrea.cuanvil.util.config.LoreEditType -import java.util.concurrent.atomic.AtomicInteger - -object AnvilLoreEditUtil { - - private const val LORE_BY_BOOK: String = "ca.lore_edit.book" - private const val LORE_BY_PAPER: String = "ca.lore_edit.paper" - - private fun hasLoreEditByBookPermission(player: Permissible): Boolean { - return !LoreEditConfigUtil.bookLoreEditNeedPermission || player.hasPermission(LORE_BY_BOOK) - } - - private fun hasLoreEditByPaperPermission(player: Permissible): Boolean { - return !LoreEditConfigUtil.paperLoreEditNeedPermission || player.hasPermission(LORE_BY_PAPER) - } - - fun handleLoreAppendByBook( - player: Permissible, - first: ItemStack, - book: BookMeta, - xpCost: AtomicInteger - ): ItemStack? { - if (!hasLoreEditByBookPermission(player)) return null - - val result = first.clone() - val meta = result.itemMeta ?: return null - val lore = if (meta.hasLore()) { - ArrayList(meta.lore!!) - } else ArrayList() - - val page = book.pages[0] - val lines = ArrayList(page.split("\n")) - val colorCost = colorLines(player, lines, LoreEditType.APPEND_BOOK) - - lore.addAll(lines) - - meta.lore = lore - result.itemMeta = meta - - if (result == first) return null - - // Handle xp - xpCost.addAndGet(colorCost) // Cost of using color - xpCost.addAndGet(lines.size * LoreEditType.APPEND_BOOK.perLineCost) // per line cost - xpCost.addAndGet(baseEditLoreXpCost(first, result, LoreEditType.APPEND_BOOK)) // Fixed cost and work penalty - - return result - } - - fun handleLoreRemoveByBook(player: Permissible, first: ItemStack, xpCost: AtomicInteger): ItemStack? { - if (!hasLoreEditByBookPermission(player)) return null - - // remove lore - val result = first.clone() - val leftMeta = result.itemMeta ?: return null - val currentLore: ArrayList = DependencyManager.stripLore(result) - if(currentLore.isEmpty()) return null - - val uncolorCost = uncolorLines(player, currentLore, LoreEditType.REMOVE_BOOK) - - leftMeta.lore = null - result.itemMeta = leftMeta - - DependencyManager.updateLore(result) - if (result == first) return null - - // Handle xp - xpCost.addAndGet(uncolorCost) - xpCost.addAndGet(currentLore.size * LoreEditType.REMOVE_BOOK.perLineCost) - xpCost.addAndGet(baseEditLoreXpCost(first, result, LoreEditType.REMOVE_BOOK)) - - return result - } - - // Return true if appended, false if removed, null if neither - fun bookLoreEditIsAppend(first: ItemStack, second: ItemStack): Boolean? { - // Test if the book & quil contain content - val meta = second.itemMeta as BookMeta? ?: return false - - var hasContent = false - if (meta.hasPages() && meta.pageCount >= 1) { - // Test if the pages is ok - for (page in meta.pages) { - if (page.isNotBlank()) { - hasContent = true - break - } - } - } - - // We don't want to "add" the first page is there is content and the first page is empty - if (hasContent) { - if (meta.pages[0].isEmpty()) return null - if (LoreEditType.APPEND_BOOK.enabled) - return true - } else if (LoreEditType.REMOVE_BOOK.enabled) { - if (!first.hasItemMeta()) return null - - val leftMeta = first.itemMeta!! - return if (leftMeta.hasLore()) false - else null - } - return null - } - - fun tryLoreEditByBook(player: HumanEntity, first: ItemStack, second: ItemStack, xpCost: AtomicInteger): ItemStack? { - val bookType = bookLoreEditIsAppend(first, second) ?: return null - - val meta = second.itemMeta as BookMeta - return if (bookType) handleLoreAppendByBook(player, first, meta, xpCost) - else handleLoreRemoveByBook(player, first, xpCost) - } - - // Return true if appended, false if removed, null if neither - fun paperLoreEditIsAppend(first: ItemStack, second: ItemStack): Boolean? { - // Test if the paper contain a display name - val meta = second.itemMeta ?: return false - - val hasContent = meta.hasDisplayName() - if (hasContent) { - if (LoreEditType.APPEND_PAPER.enabled) - return true - } else if (LoreEditType.REMOVE_PAPER.enabled) { - if (!first.hasItemMeta()) return null - - val leftMeta = first.itemMeta!! - return if (leftMeta.hasLore() && leftMeta.lore!!.isNotEmpty()) false - else null - } - return null - } - - fun handleLoreAppendByPaper( - player: Permissible, - first: ItemStack, - second: ItemStack, - xpCost: AtomicInteger - ): ItemStack? { - if (!hasLoreEditByPaperPermission(player)) return null - - val result = first.clone() - val meta = result.itemMeta?: return null - val lore = if (meta.hasLore()) { - ArrayList(meta.lore!!) - } else ArrayList() - - val appendEnd = LoreEditConfigUtil.paperLoreOrderIsEnd - - // A bit overdone to color 1 line but hey - val tempList = ArrayList(1) - tempList.add(second.itemMeta!!.displayName) - val colorCost = colorLines(player, tempList, LoreEditType.APPEND_PAPER) - - val line = tempList[0] - if (appendEnd) - lore.add(line) - else - lore.add(0, line) - - meta.lore = lore - result.itemMeta = meta - - if (result == first) return null - - // Handle xp - xpCost.addAndGet(colorCost) - xpCost.addAndGet(baseEditLoreXpCost(first, result, LoreEditType.APPEND_PAPER)) - - return result - } - - fun handleLoreRemoveByPaper(player: Permissible, first: ItemStack, xpCost: AtomicInteger): ItemStack? { - if (!hasLoreEditByPaperPermission(player)) return null - - // remove lore line - val result = first.clone() - val meta = result.itemMeta!! - - val removeEnd = LoreEditConfigUtil.paperLoreOrderIsEnd - val lore: ArrayList = DependencyManager.stripLore(result) - if(lore.isEmpty()) return null - - val line = if (removeEnd) lore.removeAt(lore.size - 1) - else lore.removeAt(0) - - meta.lore = if (lore.isEmpty()) null else lore - result.itemMeta = meta - - // Get color cost to uncolor this line - val tempList = ArrayList(1) - tempList.add(line) - val uncolorCost = uncolorLines(player, tempList, LoreEditType.REMOVE_PAPER) - - DependencyManager.updateLore(result) - if (result == first) return null - - // Handle other xp - xpCost.addAndGet(uncolorCost) - xpCost.addAndGet(baseEditLoreXpCost(first, result, LoreEditType.REMOVE_PAPER)) - - return result - } - - fun tryLoreEditByPaper( - player: HumanEntity, - first: ItemStack, - second: ItemStack, - xpCost: AtomicInteger - ): ItemStack? { - val bookType = paperLoreEditIsAppend(first, second) ?: return null - - return if (bookType) handleLoreAppendByPaper(player, first, second, xpCost) - else handleLoreRemoveByPaper(player, first, xpCost) - } - - private fun baseEditLoreXpCost( - first: ItemStack, - result: ItemStack, - editType: LoreEditType - ): Int { - var xpCost = editType.fixedCost - - xpCost += AnvilXpUtil.calculatePenalty(first, null, result, editType.useType) - return xpCost - } - - private fun colorLines(player: Permissible, lines: ArrayList, editType: LoreEditType): Int { - val canUseHex = editType.allowHexColor - val canUseColorCode = editType.allowColorCode - val colorCost = editType.useColorCost - - // Now handle color of each lines - var hasUsedColor = false - for ((index, line) in lines.withIndex()) { - val coloredLine = StringBuilder(line) - - val lineUsedColor = AnvilColorUtil.handleColor( - coloredLine, - player, - false, canUseColorCode, canUseHex, - AnvilColorUtil.ColorUseType.LORE_EDIT - ) - - if (lineUsedColor) { - hasUsedColor = true - lines[index] = coloredLine.toString() - } - } - - return if (hasUsedColor) { - colorCost - } else { - 0 - } - } - - fun uncolorLines(player: Permissible, lines: ArrayList, editType: LoreEditType): Int { - if(!editType.shouldRemoveColorOnLoreRemoval) return 0 - - // Now handle color of each lines - var hasUndidColor = false - for ((index, line) in lines.withIndex()) { - val uncoloredLine = StringBuilder(line) - - val lineUndidColor = AnvilColorUtil.revertColor( - uncoloredLine, - player, - false, true, true, - AnvilColorUtil.ColorUseType.LORE_EDIT - ) - - if (lineUndidColor) { - hasUndidColor = true - lines[index] = uncoloredLine.toString() - } - } - - return if (hasUndidColor) { - editType.removeColorCost - } else { - 0 - } - } - -} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/CustomRecipeUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/CustomRecipeUtil.kt index 3ec5e71..058b25a 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/CustomRecipeUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/CustomRecipeUtil.kt @@ -41,7 +41,7 @@ object CustomRecipeUtil { else { // test amount val resultItem = recipe.resultItem!! // we know exist as the recipe was returned to us - val maxResultAmount = resultItem.type.maxStackSize/resultItem.amount + val maxResultAmount = resultItem.maxStackSize/resultItem.amount val maxLeftAmount = leftItem.amount/recipe.leftItem!!.amount val maxRightAmount = if(rightItem == null){ maxLeftAmount } else{ rightItem.amount/recipe.rightItem!!.amount } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/MaterialUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/MaterialUtil.kt new file mode 100644 index 0000000..6b55662 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/MaterialUtil.kt @@ -0,0 +1,105 @@ +package xyz.alexcrea.cuanvil.util + +import org.bukkit.Bukkit +import org.bukkit.Material +import org.bukkit.NamespacedKey +import org.bukkit.inventory.ItemStack +import xyz.alexcrea.cuanvil.dependency.DependencyManager +import xyz.alexcrea.cuanvil.dependency.plugins.EcoItemDependencyUtil + +object MaterialUtil { + + val ItemStack?.isAir: Boolean + get() { + return this == null || this.type.isAir || this.amount == 0 + } + + val NamespacedKey?.isAir: Boolean + get() { + return Material.AIR.key == this + } + + val ItemStack.customType: NamespacedKey + get() { + if(DependencyManager.ecoEnchantCompatibility != null) { + val result = EcoItemDependencyUtil.ecoItemNamespace(this) + if(result != null) return result + } + + val itemAdder = DependencyManager.itemsAdderCompatibility + if(itemAdder != null) { + val result = itemAdder.getKey(this) + if (result != null) return result + } + + return this.type.key + } + + private fun bukkitMaterialFromKey(key: NamespacedKey): Material? { + //TODO on paper only transition Registry.MATERIAL.get(key) + return Material.matchMaterial(key.toString()) + } + + fun getMatFromKey(key: NamespacedKey): Material? { + if(DependencyManager.ecoEnchantCompatibility != null) { + val result = EcoItemDependencyUtil.ecoItemMaterialFromKey(key) + if(result != null) return result + } + + val itemAdder = DependencyManager.itemsAdderCompatibility + if(itemAdder != null) { + val result = itemAdder.fromKey(key) + if (result != null) return result.type + } + + return bukkitMaterialFromKey(key) + } + + fun itemFromKey(key: NamespacedKey): ItemStack { + if(DependencyManager.ecoEnchantCompatibility != null) { + val result = EcoItemDependencyUtil.newEcoItemstack(key) + if(result != null) return result + } + + val itemAdder = DependencyManager.itemsAdderCompatibility + if(itemAdder != null) { + val result = itemAdder.fromKey(key) + if (result != null) return result + } + + return ItemStack(bukkitMaterialFromKey(key)!!) + } + + fun materialExist(key: NamespacedKey): Boolean { + return getMatFromKey(key) != null + } + + fun getMaterialCount(): Int { + var count = Material.entries.size + if(DependencyManager.ecoEnchantCompatibility != null) { + count += EcoItemDependencyUtil.getItems().size + } + + val itemAdder = DependencyManager.itemsAdderCompatibility + if(itemAdder != null) { + count += itemAdder.idsCount().size + } + + return count + } + + fun getMaterials(): MutableList { + val all = ArrayList(Material.entries.map { it.key }) + if(DependencyManager.ecoEnchantCompatibility != null) { + all.addAll(EcoItemDependencyUtil.getItems()) + } + + val itemAdder = DependencyManager.itemsAdderCompatibility + if(itemAdder != null) { + all.addAll(itemAdder.idsCount().map { NamespacedKey.fromString(it) }) + } + + return all + } + +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt new file mode 100644 index 0000000..1763db5 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt @@ -0,0 +1,97 @@ +package xyz.alexcrea.cuanvil.util + +import dev.faststats.ErrorTracker +import dev.faststats.bukkit.BukkitContext +import dev.faststats.data.Metric +import io.delilaheve.CustomAnvil +import io.delilaheve.util.ConfigOptions +import xyz.alexcrea.cuanvil.command.DiagnosticExecutor +import xyz.alexcrea.cuanvil.config.ConfigHolder +import xyz.alexcrea.cuanvil.dependency.DependencyManager + +object MetricsUtil { + + private const val BSTATS_PLUGIN_ID = 20923 + private const val FASTSTATS_TOKEN = "fc282b048adcc71a77bc00ace49e8a81" + + private var ERROR_TRACKER: ErrorTracker? = null + private var FAST_STATS_METRICS: BukkitContext? = null + + fun loadMetrics(plugin: CustomAnvil) { + if(DependencyManager.externGuiTester.isInTest()) return + + val config = ConfigHolder.DEFAULT_CONFIG.config + val metricString = config.getString(ConfigOptions.METRIC_TYPE, MetricType.AUTO.value)!! + val metricType = MetricType.from(metricString) + + val nmsType = DiagnosticExecutor.fetchNMSType() + val isAlpha = CustomAnvil.instance.description.version.contains("dev") + if(metricType.allowBStats) { + try { + val metric = Metrics(plugin, BSTATS_PLUGIN_ID) + metric.addCustomChart(Metrics.SimplePie("nms_type") { nmsType }) + metric.addCustomChart(Metrics.SimplePie("using_alpha") { isAlpha.toString() }) + } catch (_: Exception) {} + } + + if(metricType.allowFastStats) { + // Check support java 17 (metric only work in java 17) + val versionParts = System.getProperty("java.version").split(".") + val majorVersion = versionParts[0].toInt() + if (majorVersion >= 17) try { + faststatTelemetry(plugin, nmsType, isAlpha) + } catch (_: Throwable) {} + } + } + + private fun faststatTelemetry(plugin: CustomAnvil, nmsType: String, isAlpha: Boolean) { + val config = ConfigHolder.DEFAULT_CONFIG.config + val reportErrors = config.getBoolean(ConfigOptions.METRIC_COLLECT_ERROR, true) + if(reportErrors) + ERROR_TRACKER = ErrorTracker.contextAware() + + FAST_STATS_METRICS = BukkitContext.Factory(plugin, FASTSTATS_TOKEN) + .metrics { factory -> factory + .addMetric(Metric.string("nms_type") { nmsType }) + .addMetric(Metric.bool("replace_too_expensive") { ConfigOptions.doReplaceTooExpensive }) + .addMetric(Metric.bool("using_alpha") { isAlpha }) + .create() + } + .errorTrackerService(ERROR_TRACKER) + .create() + + if(reportErrors) FAST_STATS_METRICS!!.ready() + } + + fun shutdownMetrics() { + FAST_STATS_METRICS?.shutdown() + } + + var lastError: Throwable? = null + + fun trackError(e: Throwable) { + ERROR_TRACKER?.trackError(e) + lastError = e + } + + fun trackError(message: String) { + ERROR_TRACKER?.trackError(message) + } +} + +enum class MetricType( + val value: String, + val allowBStats: Boolean, + val allowFastStats: Boolean, +) { + AUTO("auto", true, true), + BSTATS("bstat", true, false), + FAST_STATS("faststats", false, true), + DISABLED("disabled", false, false), + ; + + companion object { + fun from(value: String): MetricType = entries.find { it.value == value } ?: AUTO + } + +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/MiniMessageUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/MiniMessageUtil.kt new file mode 100644 index 0000000..c33cb9c --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/MiniMessageUtil.kt @@ -0,0 +1,33 @@ +package xyz.alexcrea.cuanvil.util + +import net.kyori.adventure.text.TextComponent +import net.kyori.adventure.text.minimessage.MiniMessage +import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver +import net.kyori.adventure.text.minimessage.tag.standard.StandardTags +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer +import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil + +object MiniMessageUtil { + + val color_only_mm = MiniMessage.builder() + .tags( + TagResolver.resolver( + StandardTags.color(), + StandardTags.decorations() + ) + ) + .build() + + val mm = if (PlatformUtil.isPaper) MiniMessage.miniMessage() + else color_only_mm + + val legacy_mm = LegacyComponentSerializer.legacySection() + val plain_text_mm = PlainTextComponentSerializer.plainText() + + // Keeping track of this as most use of this can be replaced later on v2 with pure component alternative + fun fromLegacy(legacyText: String): TextComponent { + return legacy_mm.deserialize(legacyText) + } + +} diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/NegativeSet.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/NegativeSet.kt new file mode 100644 index 0000000..a94175b --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/NegativeSet.kt @@ -0,0 +1,51 @@ +package xyz.alexcrea.cuanvil.util + +open class NegativeSet(val negate: MutableSet = HashSet()) : MutableSet { + + override fun iterator(): MutableIterator { + TODO("Not yet implemented") // can't be implemented I guess + } + + override fun add(element: T): Boolean { + return negate.remove(element) + } + + override fun remove(element: T): Boolean { + return negate.add(element) + } + + override fun addAll(elements: Collection): Boolean { + return negate.removeAll(elements.toSet()) + } + + override fun removeAll(elements: Collection): Boolean { + return negate.addAll(elements) + } + + override fun retainAll(elements: Collection): Boolean { + TODO("Not yet implemented") + } + + override fun clear() { + TODO("Not yet implemented") + } + + override fun isEmpty(): Boolean { + TODO("Not yet implemented") + } + + override val size: Int get() = TODO("Not yet implemented") + + override fun contains(element: T): Boolean { + return !negate.contains(element) + } + + override fun containsAll(elements: Collection): Boolean { + for (elm in elements) { + if(negate.contains(elm)) return false + } + + return true + } + +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/UnitRepairUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/UnitRepairUtil.kt index 2047e79..8463e27 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/UnitRepairUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/UnitRepairUtil.kt @@ -3,6 +3,7 @@ package xyz.alexcrea.cuanvil.util import org.bukkit.configuration.ConfigurationSection import org.bukkit.inventory.ItemStack import xyz.alexcrea.cuanvil.config.ConfigHolder +import xyz.alexcrea.cuanvil.util.MaterialUtil.customType object UnitRepairUtil { @@ -22,7 +23,7 @@ object UnitRepairUtil { if (other == null) return null val config = ConfigHolder.UNIT_REPAIR_HOLDER.config // Get configuration section if exist - val otherName = other.type.name.lowercase() + val otherName = other.customType.key.lowercase() var section = config.getConfigurationSection(otherName) if (section == null) { section = config.getConfigurationSection(otherName.uppercase()) @@ -44,7 +45,7 @@ object UnitRepairUtil { * If value is set to less than or equal to 0 then it will be set to default */ private fun getRepairAmount(item: ItemStack, section: ConfigurationSection, default: Double): Double? { - val itemName = item.type.name.lowercase() + val itemName = item.customType.key.lowercase() val repairValue = if (section.isDouble(itemName)) { section.getDouble(itemName) } else if (section.isDouble(itemName.uppercase())) { diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilColorUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilColorUtil.kt new file mode 100644 index 0000000..9564b2e --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilColorUtil.kt @@ -0,0 +1,306 @@ +package xyz.alexcrea.cuanvil.util.anvil + +import io.delilaheve.util.ConfigOptions +import net.kyori.adventure.text.Component +import org.bukkit.permissions.Permissible +import xyz.alexcrea.cuanvil.util.MiniMessageUtil +import java.util.regex.Matcher +import java.util.regex.Pattern + +object AnvilColorUtil { + private val HEX_PATTERN: Pattern = Pattern.compile("#[A-Fa-f0-9]{6}") // pattern to find hexadecimal string + private val TRANSFORMED_HEX_PATTERN = Pattern.compile("§x(§[0-9a-fA-F]){6}") // pattern to find minecraft hex string + + class ColorPermissions( + val canUseColorCode: Boolean, + val canUseHexColor: Boolean, + val canUseMinimessage: Boolean, + val permissible: Permissible, // source of the permission. tried to avoid needing it but meh + ) { + fun allowed(): Boolean { + return canUseColorCode || canUseHexColor || canUseMinimessage + } + + fun onlyMinimessage(): Boolean { + return canUseMinimessage && !canUseColorCode && !canUseHexColor + } + } + + fun calculatePermissions( + player: Permissible, + usePermission: Boolean, + allowColorCode: Boolean, + allowHexadecimalColor: Boolean, + allowMinimessage: Boolean, + useType: ColorUseType + ): ColorPermissions { + if (!allowColorCode && !allowHexadecimalColor && !allowMinimessage) + return ColorPermissions( + canUseColorCode = false, + canUseHexColor = false, + canUseMinimessage = false, + player, + ) + + val canUseColorCode = + allowColorCode && (!usePermission || useType.colorCodePerm == null || player.hasPermission( + useType.colorCodePerm + )) + + val canUseMinimessage = + allowMinimessage && (!usePermission || useType.minimessagePerm == null || player.hasPermission( + useType.minimessagePerm + )) + + val canUseHexColor = + allowHexadecimalColor && (!usePermission || useType.hexColorPerm == null || player.hasPermission( + useType.hexColorPerm + )) + + return ColorPermissions(canUseColorCode, canUseHexColor, canUseMinimessage, player) + } + + fun renamePermission(player: Permissible): ColorPermissions { + return calculatePermissions(player, + ConfigOptions.permissionNeededForColor, + ConfigOptions.allowColorCode, ConfigOptions.allowHexadecimalColor, ConfigOptions.allowMinimessage, + ColorUseType.RENAME) + } + + /** + * Color a string depending on permitted use + * @return colored component or null if nothing has been colored + */ + fun handleColor( + textToColorText: String, + permission: ColorPermissions, + + ): Component? { + if (!permission.allowed()) return null + + val textToColor = StringBuilder(textToColorText) + var useColor = false + // Handle color code + if (permission.canUseColorCode) { // maybe should use LegacyComponentSerializer ? + var nbReplacement = replaceAll(textToColor, "&", "§", 2) + nbReplacement -= 2 * replaceAll(textToColor, "§§", "&", 2) + + if (nbReplacement > 0) { + useColor = true + + if (ConfigOptions.usePerColorCodePermission) + filterPermissibleColorCode(textToColor, permission.permissible) + } + } + + if (permission.canUseHexColor) { + val nbReplacement = replaceHexToColor(textToColor, 7, permission.canUseMinimessage) + + if (nbReplacement > 0) useColor = true + } + + val previousStr = textToColor.toString() + var result: Component = MiniMessageUtil.legacy_mm.deserialize(previousStr) + if (permission.canUseMinimessage) { + // we dance with formats here + val toMinimessage = MiniMessageUtil.mm.serialize(result) + val hackySolution = toMinimessage.replace("\\<", "<") + val fromMinimessage = MiniMessageUtil.mm.deserialize(hackySolution) + val asPlain = MiniMessageUtil.plain_text_mm.serialize(fromMinimessage) + + if (previousStr != asPlain) { + useColor = true + result = fromMinimessage + } + } + + return if (useColor) result + else null + } + + private fun filterPermissibleColorCode(textToColor: StringBuilder, player: Permissible) { + var index = 0 + while (true) { + index = textToColor.indexOf('§', index) + if (index == -1 || index == textToColor.length - 1) return + + val next = textToColor[index + 1] + // check permission for this color + if(!player.hasPermission("ca.color.code.$next")) + textToColor.replace(index, index + 1, "&") + + index++ + } + } + + /** + * Best effort to revert a component to the smallest allowed string + * that would result in it getting closest as possible to handleColor + * with current set of permitted use + * @return a new component if had any change. null otherwise + */ + fun revertColorSmallest( + component: Component?, + permission: ColorPermissions + ): String? { + if (!permission.allowed() || component == null) return null + + val transformed = MiniMessageUtil.mm.serialize(component) + val plainTransform = MiniMessageUtil.plain_text_mm.serialize(component) + if (transformed == plainTransform) return null + if (permission.onlyMinimessage()) { + return transformed + } + + // smol dance so we transform the component that may contain other tag into only decoration & color for legacy + val coloredMessage = MiniMessageUtil.color_only_mm.deserialize(transformed) + val legacyMessage = StringBuilder(MiniMessageUtil.legacy_mm.serialize(coloredMessage)) + + // Reverse hex pattern + if (permission.canUseHexColor) { + replaceColorToHex(legacyMessage, 14) + } + + // Reverse color pattern + if (permission.canUseColorCode) { + replaceAll(legacyMessage, "&", "&&", 1) + replaceAll(legacyMessage, "§", "&", 2) + } + + // In case we still has some § around by lack of permission we need to convert it back from legacy + // In other word it's time for dance #3 + val fromLegacy = MiniMessageUtil.legacy_mm.deserialize(legacyMessage.toString()) + val middleGround = MiniMessageUtil.mm.serialize(fromLegacy) + val hackySolutionStb = StringBuilder(middleGround) + replaceAll(hackySolutionStb, "\\<", "<", 2) + val hackySolution = hackySolutionStb.toString() + + val result: String = + if (permission.canUseMinimessage) hackySolution + else MiniMessageUtil.mm.stripTags(hackySolution) + + return if (result == plainTransform) null + else result + } + + /** + * Replace every instance of "from" to "to". + * @param builder The builder to replace the string from. + * @param from The source that should be replaced. + * @param to The string that should replace. + * @param endOffset Amount of character that should be ignored at the end. + * @return The number of replacement was that was done. + */ + private fun replaceAll(builder: java.lang.StringBuilder, from: String, to: String, endOffset: Int): Int { + var index = builder.indexOf(from) + var numberOfChanges = 0 + + while (index != -1 && index < builder.length - endOffset) { + builder.replace(index, index + from.length, to) + index += to.length + index = builder.indexOf(from, index) + + numberOfChanges += 1 + } + + return numberOfChanges + } + + /** + * Replace every hex color formatted like #000000 to the minecraft format + * @param builder The builder to replace the hex color from. + * @param endOffset Amount of character that should be ignored at the end. + * @return The number of replacement was that was done. + */ + private fun replaceHexToColor(builder: StringBuilder, endOffset: Int, checkTag: Boolean): Int { + val matcher: Matcher = HEX_PATTERN.matcher(builder) + + var numberOfChanges = 0 + var startIndex = 0 + + while (matcher.find(startIndex)) { + startIndex = matcher.start() + if (startIndex >= builder.length - endOffset) break //HOW AND WHERE WOULD THIS HAPPEN ????? + if (checkTag && isInTag(builder, startIndex)) { + startIndex += 1 // Avoid infinite loop + continue + } + + builder.replace(startIndex, startIndex + 1, "§x") + startIndex += 2 + for (i in 0..5) { + builder.insert(startIndex, '§') + startIndex += 2 + } + + numberOfChanges += 1 + } + + return numberOfChanges + } + + // Simple check if < > with some smart check like <> > not taken into account + // This is easily bypassable but if the player want to bypass he has better alternative + // AKA should avoid getting into any tag + private fun isInTag(builder: StringBuilder, index: Int): Boolean { + // Check left tag we have < after last > + val left = builder.slice(0..index) + val leftIndex = left.lastIndexOf("<") + var rightIndex = left.lastIndexOf(">") + + // last < do not exist or is before last > + if (leftIndex == -1 || rightIndex > leftIndex) return false + + val right = builder.slice(index..") + + // first > do not exist or is after first < (if exist) + if (rightIndex == -1 || (newleftIndex != -1 && newleftIndex < rightIndex)) return false + + // Then finally we use minimessage to check for tag + val expectedTag = builder.substring(leftIndex, newleftIndex + index + 1) + val notag = MiniMessageUtil.mm.stripTags(expectedTag) + + return notag != expectedTag + } + + /** + * Replace every hex color from the minecraft format to a format like #000000 + * @param builder The builder to replace the minecraft hex color from. + * @param endOffset Amount of character that should be ignored at the end. + * @return The number of replacement was that was done. + */ + private fun replaceColorToHex(builder: StringBuilder, endOffset: Int): Int { + val matcher: Matcher = TRANSFORMED_HEX_PATTERN.matcher(builder) + + var numberOfChanges = 0 + var startIndex = 0 + + while (matcher.find(startIndex)) { + startIndex = matcher.start() + if (startIndex >= builder.length - endOffset) break //HOW AND WHERE WOULD THIS HAPPEN ????? + + builder.replace(startIndex, startIndex + 2, "#") + startIndex += 1 + for (i in 0..5) { + builder.deleteCharAt(startIndex) + startIndex += 1 + } + + numberOfChanges += 1 + } + + return numberOfChanges + } + + enum class ColorUseType( + val colorCodePerm: String?, + val hexColorPerm: String?, + val minimessagePerm: String? + ) { + RENAME("ca.color.code", "ca.color.hex", "ca.rename.minimessage"), + LORE_EDIT(null, null, null) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilLoreEditUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilLoreEditUtil.kt new file mode 100644 index 0000000..a021b46 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilLoreEditUtil.kt @@ -0,0 +1,368 @@ +package xyz.alexcrea.cuanvil.util.anvil + +import net.kyori.adventure.text.Component +import org.bukkit.entity.HumanEntity +import org.bukkit.inventory.ItemStack +import org.bukkit.inventory.meta.BookMeta +import org.bukkit.permissions.Permissible +import xyz.alexcrea.cuanvil.anvil.AnvilCost +import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.LoreEditResult +import xyz.alexcrea.cuanvil.dependency.DependencyManager +import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil.componentLore +import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil.setComponentLore +import xyz.alexcrea.cuanvil.util.MiniMessageUtil +import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil +import xyz.alexcrea.cuanvil.util.config.LoreEditType +import java.util.* +import java.util.concurrent.atomic.AtomicReference + +object AnvilLoreEditUtil { + + private const val LORE_BY_BOOK: String = "ca.lore_edit.book" + private const val LORE_BY_PAPER: String = "ca.lore_edit.paper" + + private fun hasLoreEditByBookPermission(player: Permissible): Boolean { + return !LoreEditConfigUtil.bookLoreEditNeedPermission || player.hasPermission(LORE_BY_BOOK) + } + + private fun hasLoreEditByPaperPermission(player: Permissible): Boolean { + return !LoreEditConfigUtil.paperLoreEditNeedPermission || player.hasPermission(LORE_BY_PAPER) + } + + fun handleLoreAppendByBook( + player: Permissible, + first: ItemStack, + book: BookMeta, + cost: AnvilCost + ): ItemStack? { + if (!hasLoreEditByBookPermission(player)) return null + + val result = first.clone() + val meta = result.itemMeta ?: return null + val lore = meta.componentLore() + + val page = book.pages[0] + val lines = ArrayList(page.split("\n")) + val outLines = ArrayList(lines.size) + val colorCost = colorLines( + player, LoreEditType.APPEND_BOOK, + lines, outLines + ) + + lore.addAll(outLines) + + meta.setComponentLore(lore) + result.itemMeta = meta + + if (result == first) return null + + // Handle xp + cost.lore = colorCost // Cost of using color + cost.lore += outLines.size * LoreEditType.APPEND_BOOK.perLineCost // per line cost + baseEditLoreXpCost(cost, first, result, LoreEditType.APPEND_BOOK) // Fixed cost and work penalty + + return result + } + + fun handleLoreRemoveByBook(player: Permissible, first: ItemStack, cost: AnvilCost): ItemStack? { + if (!hasLoreEditByBookPermission(player)) return null + + // remove lore + val result = first.clone() + val leftMeta = result.itemMeta ?: return null + val currentLore = DependencyManager.stripLore(result) + if (currentLore.isEmpty()) return null + + val uncolorCost = uncolorLines(player, currentLore, LoreEditType.REMOVE_BOOK) + + leftMeta.lore = null + result.itemMeta = leftMeta + + DependencyManager.updateLore(result) + if (result == first) return null + + // Handle xp + cost.lore = uncolorCost + cost.lore += currentLore.size * LoreEditType.REMOVE_BOOK.perLineCost + baseEditLoreXpCost(cost, first, result, LoreEditType.REMOVE_BOOK) + + return result + } + + // Return true if appended, false if removed, null if neither + fun bookLoreEditIsAppend(first: ItemStack, second: ItemStack): Boolean? { + // Test if the book & quil contain content + val meta = second.itemMeta as BookMeta? ?: return false + + var hasContent = false + if (meta.hasPages() && meta.pageCount >= 1) { + // Test if the pages is ok + for (page in meta.pages) { + if (page.isNotBlank()) { + hasContent = true + break + } + } + } + + // We don't want to "add" the first page is there is content and the first page is empty + if (hasContent) { + if (meta.pages[0].isEmpty()) return null + if (LoreEditType.APPEND_BOOK.enabled) + return true + } else if (LoreEditType.REMOVE_BOOK.enabled) { + if (!first.hasItemMeta()) return null + + val leftMeta = first.itemMeta!! + return if (leftMeta.hasLore()) false + else null + } + return null + } + + fun tryLoreEditByBook(player: HumanEntity, first: ItemStack, second: ItemStack): LoreEditResult { + val isAppend = bookLoreEditIsAppend(first, second) ?: return LoreEditResult.EMPTY + val type = if (isAppend) LoreEditType.APPEND_BOOK else LoreEditType.REMOVE_BOOK + + val meta = second.itemMeta as BookMeta + val cost = AnvilCost() + val item = if (isAppend) + handleLoreAppendByBook(player, first, meta, cost) + else handleLoreRemoveByBook(player, first, cost) + + return LoreEditResult(item, cost, type) + } + + // Return true if appended, false if removed, null if neither + fun paperLoreEditIsAppend(first: ItemStack, second: ItemStack): Boolean? { + // Test if the paper contain a display name + val meta = second.itemMeta ?: return false + + val hasContent = meta.hasDisplayName() + if (hasContent) { + if (LoreEditType.APPEND_PAPER.enabled) + return true + } else if (LoreEditType.REMOVE_PAPER.enabled) { + if (!first.hasItemMeta()) return null + + val leftMeta = first.itemMeta!! + return if (leftMeta.hasLore() && leftMeta.lore!!.isNotEmpty()) false + else null + } + return null + } + + fun handleLoreAppendByPaper( + player: Permissible, + first: ItemStack, + second: ItemStack, + cost: AnvilCost + ): ItemStack? { + if (!hasLoreEditByPaperPermission(player)) return null + + val result = first.clone() + val meta = result.itemMeta ?: return null + val lore = meta.componentLore() + + val appendEnd = LoreEditConfigUtil.paperLoreOrderIsEnd + + // A bit overdone to color 1 line but hey + val outList = ArrayList(1) + val colorCost = colorLines( + player, LoreEditType.APPEND_PAPER, + Collections.singletonList(second.itemMeta!!.displayName), + outList + ) + + val line = outList[0] + if (appendEnd) + lore.add(line) + else + lore.add(0, line) + + meta.setComponentLore(lore) + result.itemMeta = meta + + if (result == first) return null + + // Handle xp + cost.lore = colorCost + baseEditLoreXpCost(cost, first, result, LoreEditType.APPEND_PAPER) + + return result + } + + fun handleLoreRemoveByPaper(player: Permissible, first: ItemStack, cost: AnvilCost): ItemStack? { + if (!hasLoreEditByPaperPermission(player)) return null + + // remove lore line + val result = first.clone() + val meta = result.itemMeta!! + + val removeEnd = LoreEditConfigUtil.paperLoreOrderIsEnd + val lore = DependencyManager.stripLore(result) + if (lore.isEmpty()) return null + + val line = if (removeEnd) lore.removeAt(lore.size - 1) + else lore.removeAt(0) + + meta.lore = null + result.itemMeta = meta + + // Update lore but make sure custom lore is put last + DependencyManager.updateLore(result) + + val finalLore = ArrayList() + finalLore.addAll(meta.componentLore()) + finalLore.addAll(lore) + + meta.setComponentLore(finalLore) + result.itemMeta = meta + if (result == first) return null + + // Get color cost to uncolor this line + val uncolorCost = uncolorLine(player, line, LoreEditType.REMOVE_PAPER) + + // Handle other xp + cost.lore = uncolorCost + baseEditLoreXpCost(cost, first, result, LoreEditType.REMOVE_PAPER) + + return result + } + + fun tryLoreEditByPaper( + player: HumanEntity, + first: ItemStack, + second: ItemStack + ): LoreEditResult { + val isAppend = paperLoreEditIsAppend(first, second) ?: return LoreEditResult.EMPTY + val type = if (isAppend) LoreEditType.APPEND_BOOK else LoreEditType.REMOVE_BOOK + + val cost = AnvilCost() + val item = if (isAppend) + handleLoreAppendByPaper(player, first, second, cost) + else handleLoreRemoveByPaper(player, first, cost) + + return LoreEditResult(item, cost, type) + } + + private fun baseEditLoreXpCost( + cost: AnvilCost, + first: ItemStack, + result: ItemStack, + editType: LoreEditType + ) { + cost.lore += editType.fixedCost + + cost.workPenalty = AnvilXpUtil.calculatePenalty(first, null, result, editType.useType) + } + + fun colorPermission(player: Permissible, editType: LoreEditType): AnvilColorUtil.ColorPermissions { + return AnvilColorUtil.calculatePermissions( + player, + false, + editType.allowColorCode, + editType.allowHexColor, + editType.allowMinimessage, + AnvilColorUtil.ColorUseType.LORE_EDIT + ) + } + + private fun colorLine(line: String, permission: AnvilColorUtil.ColorPermissions): Component? { + return AnvilColorUtil.handleColor( + line, + permission + ) + } + + private fun colorLines( + player: Permissible, editType: LoreEditType, + lines: List, outLines: MutableList + ): Int { + val permission = colorPermission(player, editType) + val colorCost = editType.useColorCost + + // Handle color and minimessage of each lines + var hasUsedColor = false + for (line in lines) { + val component = colorLine(line, permission) + + if (component != null) { + hasUsedColor = true + outLines.add(component) + } else { + outLines.add(Component.text(line)) + } + } + + return if (hasUsedColor) colorCost + else 0 + } + + fun uncolorLines(player: Permissible, lines: MutableList, editType: LoreEditType): Int { + val permission = colorPermission(player, editType) + + // Now handle color of each lines + var hasUndidColor = false + for ((index, line) in lines.withIndex()) { + if (line == null) { + lines[index] = null + continue + } + + val clearedLine = AnvilColorUtil.revertColorSmallest( + line, + permission + ) + + val result: String + if (clearedLine != null) { + hasUndidColor = true + result = clearedLine + } else { + result = MiniMessageUtil.plain_text_mm.serialize(line) + } + + lines[index] = MiniMessageUtil.plain_text_mm.deserialize(result) + } + + return if (hasUndidColor) { + editType.removeColorCost + } else { + 0 + } + } + + // do not output the uncolored line... + fun uncolorLine(player: Permissible, line: Component?, editType: LoreEditType): Int { + return uncolorLine(player, AtomicReference(line), editType) + } + + fun uncolorLine(player: Permissible, line: AtomicReference, editType: LoreEditType): Int { + val coloredComponent = line.get() ?: return 0 + val permission = colorPermission(player, editType) + + val clearedLine = AnvilColorUtil.revertColorSmallest( + coloredComponent, + permission + ) + + var hasUndidColor = false + val result: String + if (clearedLine != null) { + hasUndidColor = true + result = clearedLine + } else { + // Remove extra tags + result = MiniMessageUtil.plain_text_mm.serialize(coloredComponent) + } + line.set(MiniMessageUtil.plain_text_mm.deserialize(result)) + + return if (hasUndidColor) { + editType.removeColorCost + } else { + 0 + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilUseTypeUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilUseTypeUtil.kt similarity index 96% rename from src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilUseTypeUtil.kt rename to src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilUseTypeUtil.kt index c72a35a..a9c7f39 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilUseTypeUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilUseTypeUtil.kt @@ -1,4 +1,4 @@ -package xyz.alexcrea.cuanvil.util +package xyz.alexcrea.cuanvil.util.anvil import io.delilaheve.util.ConfigOptions diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilXpUtil.kt similarity index 59% rename from src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt rename to src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilXpUtil.kt index 9220ee3..4846f31 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilXpUtil.kt @@ -1,4 +1,4 @@ -package xyz.alexcrea.cuanvil.util +package xyz.alexcrea.cuanvil.util.anvil import io.delilaheve.CustomAnvil import io.delilaheve.util.ConfigOptions @@ -14,46 +14,59 @@ import org.bukkit.inventory.InventoryView import org.bukkit.inventory.ItemStack import org.bukkit.inventory.meta.Repairable import org.bukkit.persistence.PersistentDataType +import xyz.alexcrea.cuanvil.anvil.AnvilCost +import xyz.alexcrea.cuanvil.anvil.AnvilUseType import xyz.alexcrea.cuanvil.config.ConfigHolder import xyz.alexcrea.cuanvil.dependency.DependencyManager +import xyz.alexcrea.cuanvil.dependency.economy.EconomyManager import xyz.alexcrea.cuanvil.group.ConflictType -import kotlin.math.min +import xyz.alexcrea.cuanvil.util.AnvilTitleUtil +import xyz.alexcrea.cuanvil.util.dialog.AnvilRenameDialogUtil object AnvilXpUtil { const val EXCLUSIVE_PENALTY_PREFIX = "repair_cost" + /** + * Display the required cost (either as xp or as ) + */ + fun setAnvilInvCost( + inventory: AnvilInventory, + view: InventoryView, + player: Player, + cost: AnvilCost, + ignoreRules: Boolean = false + ) { + if (ConfigOptions.shouldUseMoney(player)) { + cost.isMonetary = true + setAnvilPrice(inventory, view, player, cost) + } else + setAnvilInvXp(inventory, view, player, cost.filteredXpCost(ignoreRules), ignoreRules) + } + + fun maximumXpCost(ignoreRules: Boolean = false): Int { + return if (ConfigOptions.doRemoveCostLimit || ignoreRules) { + Int.MAX_VALUE + } else { + ConfigOptions.maxAnvilCost + 1 + } + } + /** * Display xp needed for the work on the anvil inventory */ - fun setAnvilInvXp( + private fun setAnvilInvXp( inventory: AnvilInventory, view: InventoryView, player: HumanEntity, anvilCost: Int, ignoreRules: Boolean = false ) { - // Test repair cost limit - val finalAnvilCost = if ( - !ignoreRules && - !ConfigOptions.doRemoveCostLimit && - ConfigOptions.doCapCost - ) { - min(anvilCost, ConfigOptions.maxAnvilCost) - } else { - anvilCost - } - - val maximumRepairCost = - if (ConfigOptions.doRemoveCostLimit || ignoreRules) { - Int.MAX_VALUE - } else { - ConfigOptions.maxAnvilCost + 1 - } + val maximumRepairCost = maximumXpCost(ignoreRules) // Try first just in case another plugin, or the test need this inventory.maximumRepairCost = maximumRepairCost - inventory.repairCost = finalAnvilCost + inventory.repairCost = anvilCost // TODO for 2.x.x use anvil view & set directly there /* Because Minecraft likes to have the final say in the repair cost displayed @@ -64,21 +77,65 @@ object AnvilXpUtil { ) { // retry after a tick inventory.maximumRepairCost = maximumRepairCost - inventory.repairCost = finalAnvilCost + inventory.repairCost = anvilCost // TODO for 2.x.x use anvil view & set directly there if (player !is Player) return@scheduleOnEntity if (player.gameMode != GameMode.CREATIVE) { val bypassToExpensive = (ConfigOptions.doReplaceTooExpensive) && - (finalAnvilCost >= 40) && - finalAnvilCost < inventory.maximumRepairCost + (anvilCost >= 40) && + anvilCost < inventory.maximumRepairCost DependencyManager.packetManager.setInstantBuild(player, bypassToExpensive) } player.updateInventory() + } + } + /** + * Display monetary cost needed for the work on the anvil inventory + */ + private fun setAnvilPrice( + inventory: AnvilInventory, + view: InventoryView, + player: Player, + cost: AnvilCost, + ) { + val finalCost = cost.asMonetaryCost() + + val has = player.gameMode == GameMode.CREATIVE || + EconomyManager.economy!!.has(player, finalCost) + + val text = "Cost: " + (if (has) "§2" else "§4") + + EconomyManager.economy!!.format(finalCost) + AnvilTitleUtil.rename( + view, text, + player, + AnvilRenameDialogUtil.anvilRenameDialog, + CustomAnvil.instance + ) + + clearAnvilXpCost(inventory, view, player) + } + + private fun clearAnvilXpCost( + inventory: AnvilInventory, + view: InventoryView, + player: HumanEntity, + ) { + // TODO for 2.x.x use anvil view & set directly there + inventory.repairCost = 0 + + // retry after a tick + DependencyManager.scheduler.scheduleOnEntity( + CustomAnvil.instance, player + ) { + inventory.repairCost = 0 + + if (player !is Player) return@scheduleOnEntity + player.updateInventory() } } @@ -128,6 +185,16 @@ object AnvilXpUtil { return resultSum } + fun onNoResult(player: HumanEntity, view: InventoryView) { + if (ConfigOptions.shouldUseMoney(player)) + AnvilTitleUtil.rename( + view, "Repair & Name", + player, + AnvilRenameDialogUtil.anvilRenameDialog, + CustomAnvil.instance + ) + } + private fun exclusivePenaltyKey(useType: AnvilUseType): NamespacedKey { return NamespacedKey(CustomAnvil.instance, "${EXCLUSIVE_PENALTY_PREFIX}_${useType.typeName}") } @@ -159,10 +226,8 @@ object AnvilXpUtil { * Function to calculate right enchantment values * it include enchantment placed on final item and conflicting enchantment */ - fun getRightValues(right: ItemStack, result: ItemStack): Int { + fun getRightValues(right: ItemStack, result: ItemStack, cost: AnvilCost) { // Calculate right value and illegal enchant penalty - var illegalPenalty = 0 - var rightValue = 0 val rightIsFormBook = right.isEnchantedBook() val resultEnchs = result.findEnchantments() @@ -180,7 +245,7 @@ object AnvilXpUtil { resultEnchsKeys.remove(enchantment.key) if (ConflictType.ENCHANTMENT_CONFLICT == conflictType) { - illegalPenalty += ConfigOptions.sacrificeIllegalCost + cost.illegalPenalty += ConfigOptions.sacrificeIllegalCost CustomAnvil.verboseLog("Big conflict. Adding illegal price penalty") } continue @@ -191,16 +256,53 @@ object AnvilXpUtil { val enchantmentMultiplier = ConfigOptions.enchantmentValue(enchantment.key, rightIsFormBook) val value = resultLevel * enchantmentMultiplier CustomAnvil.log("Value for ${enchantment.key.enchantmentName} level ${enchantment.value} is $value ($resultLevel * $enchantmentMultiplier)") - rightValue += value + cost.enchantment += value } CustomAnvil.log( "Calculated right values: " + - "rightValue: $rightValue, " + - "illegalPenalty: $illegalPenalty" + "rightValue: ${cost.enchantment}, " + + "illegalPenalty: ${cost.illegalPenalty}" ) + } - return rightValue + illegalPenalty + /** + * Calculate the maximum level reachable with this amount of `xp` + * This is equivalent of the displayed level on client + * @author provided by kFor + */ + fun calculateLevelForXp(xp: Int): Int { + return when { + xp <= 352 -> (Math.sqrt((xp + 9).toDouble()) - 3).toInt() + xp <= 1507 -> { + val inner = (2.0 / 5.0) * (xp - 7839.0 / 40.0) + (81.0 / 10.0 + Math.sqrt(inner)).toInt() + } + + else -> { + val inner = (2.0 / 9.0) * (xp - 54215.0 / 72.0) + (325.0 / 18.0 + Math.sqrt(inner)).toInt() + } + } + } + + /** + * Calculate the minimum level necessary to have at least `xp` + */ + fun calculateMinimumLevelForXp(xp: Int): Int { + return calculateLevelForXp(xp - 1) + 1 + } + + /** + * Calculate the minimum amount of xp necessary to reach `level` + * @author provided by kFor + */ + fun calculateXpForLevel(level: Int): Int { + return when { + level <= 16 -> (level * level + 6 * level) + level <= 31 -> (2.5 * level * level - 40.5 * level + 360).toInt() + else -> (4.5 * level * level - 162.5 * level + 2220).toInt() + } } } \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/config/LoreEditConfigUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/config/LoreEditConfigUtil.kt index f5758dc..9d0eb6a 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/config/LoreEditConfigUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/config/LoreEditConfigUtil.kt @@ -17,9 +17,9 @@ object LoreEditConfigUtil { // Color configs path const val ALLOW_COLOR_CODE = "allow_color_code" const val ALLOW_HEX_COLOR = "allow_hexadecimal_color" + const val ALLOW_MINIMESSAGE = "allow_minimessage" const val USE_COLOR_COST = "use_cost" - const val REMOVE_COLOR_ON_LORE_REMOVE = "remove_color_on_remove" const val REMOVE_COLOR_COST = "remove_color_cost" // Lore order config path @@ -42,9 +42,9 @@ object LoreEditConfigUtil { // Color configs defaults const val DEFAULT_ALLOW_COLOR_CODE = true const val DEFAULT_ALLOW_HEX_COLOR = true + const val DEFAULT_ALLOW_MINIMESSAGE = true const val DEFAULT_USE_COLOR_COST = 0 - const val DEFAULT_REMOVE_COLOR_ON_LORE_REMOVE = false const val DEFAULT_REMOVE_COLOR_COST = 0 // Lore order config default diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/config/LoreEditType.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/config/LoreEditType.kt index ed5ef1b..c094c71 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/config/LoreEditType.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/config/LoreEditType.kt @@ -1,16 +1,16 @@ package xyz.alexcrea.cuanvil.util.config -import xyz.alexcrea.cuanvil.util.AnvilUseType +import xyz.alexcrea.cuanvil.anvil.AnvilUseType import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.ALLOW_COLOR_CODE import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.ALLOW_HEX_COLOR +import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.ALLOW_MINIMESSAGE import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.DEFAULT_ALLOW_COLOR_CODE import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.DEFAULT_ALLOW_HEX_COLOR +import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.DEFAULT_ALLOW_MINIMESSAGE import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.DEFAULT_REMOVE_COLOR_COST -import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.DEFAULT_REMOVE_COLOR_ON_LORE_REMOVE import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.DEFAULT_USE_COLOR_COST import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.REMOVE_COLOR_COST import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.REMOVE_COLOR_COST_RANGE -import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.REMOVE_COLOR_ON_LORE_REMOVE import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.USE_COLOR_COST import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.USE_COLOR_COST_RANGE import xyz.alexcrea.cuanvil.config.ConfigHolder.DEFAULT_CONFIG as CONFIG @@ -18,20 +18,23 @@ import xyz.alexcrea.cuanvil.config.ConfigHolder.DEFAULT_CONFIG as CONFIG enum class LoreEditType( val rootPath: String, val useType: AnvilUseType, + val isBook: Boolean, val isAppend: Boolean, val isMultiLine: Boolean, ) { - APPEND_BOOK(AnvilUseType.LORE_EDIT_BOOK_APPEND, true, true), - REMOVE_BOOK(AnvilUseType.LORE_EDIT_BOOK_REMOVE, false, true), - APPEND_PAPER(AnvilUseType.LORE_EDIT_PAPER_APPEND, true, false), - REMOVE_PAPER(AnvilUseType.LORE_EDIT_PAPER_REMOVE, false, false), + APPEND_BOOK(AnvilUseType.LORE_EDIT_BOOK_APPEND, true, true, true), + REMOVE_BOOK(AnvilUseType.LORE_EDIT_BOOK_REMOVE, true, false, true), + APPEND_PAPER(AnvilUseType.LORE_EDIT_PAPER_APPEND, false, true, false), + REMOVE_PAPER(AnvilUseType.LORE_EDIT_PAPER_REMOVE, false, false, false), ; constructor( useType: AnvilUseType, + isPaper: Boolean, isAppend: Boolean, isMultiLine: Boolean, - ) : this(useType.path, useType, isAppend, isMultiLine) + ) : this(useType.path, useType, + isPaper, isAppend, isMultiLine) /** * If this edit type is enabled @@ -79,27 +82,35 @@ enum class LoreEditType( } /** - * Allow usage of color code on lore add + * Allow usage or removal of color code */ val allowColorCode: Boolean get() { - if (!isAppend) throw IllegalStateException("Can only call with an append edit type") return CONFIG .config .getBoolean("$rootPath.$ALLOW_COLOR_CODE", DEFAULT_ALLOW_COLOR_CODE) } /** - * Allow usage of hexadecimal color on lore add + * Allow usage or removal of hexadecimal color */ val allowHexColor: Boolean get() { - if (!isAppend) throw IllegalStateException("Can only call with an append edit type") return CONFIG .config .getBoolean("${rootPath}.$ALLOW_HEX_COLOR", DEFAULT_ALLOW_HEX_COLOR) } + /** + * Allow usage or removal of minimessage on lore add + */ + val allowMinimessage: Boolean + get() { + return CONFIG + .config + .getBoolean("${rootPath}.$ALLOW_MINIMESSAGE", DEFAULT_ALLOW_MINIMESSAGE) + } + /** * Cost when using either color code and hex color on lore add */ @@ -114,17 +125,6 @@ enum class LoreEditType( } - /** - * Should the color code & hex color should get removed on lore remove - */ - val shouldRemoveColorOnLoreRemoval: Boolean - get() { - if (isAppend) throw IllegalStateException("Can only call with a remove edit type") - return CONFIG - .config - .getBoolean("${rootPath}.$REMOVE_COLOR_ON_LORE_REMOVE", DEFAULT_REMOVE_COLOR_ON_LORE_REMOVE) - } - /** * Cost when using either color code and hex color on lore remove */ diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/dialog/AnvilRenameDialogUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/dialog/AnvilRenameDialogUtil.kt new file mode 100644 index 0000000..07d4e08 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/dialog/AnvilRenameDialogUtil.kt @@ -0,0 +1,55 @@ +package xyz.alexcrea.cuanvil.util.dialog + +import io.delilaheve.CustomAnvil +import io.delilaheve.util.ConfigOptions +import org.bukkit.entity.HumanEntity +import org.bukkit.event.inventory.PrepareAnvilEvent +import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil +import xyz.alexcrea.cuanvil.dialog.AnvilRenameDialog +import xyz.alexcrea.cuanvil.dialog.AnvilRenameDialogImpl +import xyz.alexcrea.cuanvil.update.UpdateUtils +import xyz.alexcrea.cuanvil.util.anvil.AnvilColorUtil + +object AnvilRenameDialogUtil { + + val anvilRenameDialog: AnvilRenameDialog; + + init { + val version = UpdateUtils.currentMinecraftVersion() + anvilRenameDialog = (if(!PlatformUtil.isPaper || + (version.major <= 1 && version.minor <= 21 && version.patch <= 6)) { + NoImplAnvilRenameDialog() + } else { + AnvilRenameDialogImpl({ player, component -> AnvilColorUtil.revertColorSmallest( + component, AnvilColorUtil.renamePermission(player) + ) }, + { ConfigOptions.shouldKeepRenameText }, + { ConfigOptions.renameDialogMaxSize }, + CustomAnvil.instance, + ) + }) + } + + class NoImplAnvilRenameDialog: AnvilRenameDialog { + + override fun canSendDialog(): Boolean { + return false + } + + override fun tryShowDialog( + player: HumanEntity, + event: PrepareAnvilEvent + ) {} + + override fun closeInventory(player: HumanEntity) {} + + override fun currentText(player: HumanEntity): String? { + return null + } + + override fun isOpenFor(player: HumanEntity): Boolean { + return false + } + + } +} \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 9c1ad67..7d6e396 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,8 +1,21 @@ # -# It is recommended that you use /configanvil to edit theses config. +# It is recommended that you use /configanvil to edit most of these config. # You can still manually edit here if you like to. but if you do, don't forget to /anvilconfigreload after you changes ! # +# What service of metric should custom anvil use +# Custom anvil collect generic information like server minecraft version, type, etc... +# It can also collect error information if error is happening (currently faststats only) +# It can also be disabled +# Please refer to README for public metric link +# Possible options: auto, bstat, faststats, disabled (auto by default) +metric_type: auto + +# Allow to report errors made caused by this plugin (only for faststats) +# This allows me to fix potentials issue that I'm not aware of +# Accept true or false (true by default) +metric_collect_errors: true + # All anvil cost will be capped to limit_repair_value if enabled. # # In other words: @@ -59,8 +72,24 @@ sacrifice_illegal_enchant_cost: 1 # # Color code are prefixed by "&" and hexadecimal color by "#". # Color code will not be applied if it colors nothing. "&&" can be used to write "&". +# For minimessage search for minimessage formating https://docs.papermc.io/adventure/minimessage/format/ +# Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin +# but any global tag will be allowed later when v2 release allow_color_code: false allow_hexadecimal_color: false +allow_minimessage: false + +# This enables restricting color code for player having specific permission +# It requires allow_color_code enabled for... obvious reasons +# +# For example: if player want to use "&aHello" it will be required that the player has +# the permission "ca.color.code.a" as he used the color code "a" +# In general permission to give to the player is "ca.color.code.[code]" +# where [code] is the color code you wish to allow the player +# +# It is kinda of useless when minimessage is supported as players would be able to bypass +# that using the equivalent minimessage tag +per_color_code_permission: false # Toggle if color should only be applicable if the player a certain permission. # @@ -72,10 +101,23 @@ permission_needed_for_color: true # Valid values include 0 to 1000. use_of_color_cost: 0 -# Default limit to apply to any enchants missing from enchant_limits +# Dialogue rename menu make use of dialog menu to allow bigger rename +# You can also change the maximum size and set it to -1 or less for maximum # -# Valid values include 1 to 1000 -default_limit: 5 +# This feature only work on paper 1.21.7 or later +# +# At the moment only english is available for this menu... sorry ! +# +# CustomAnvil use "ca.rename.dialog" when permission +enable_dialog_rename: false +dialog_rename_max_size: 256 +permission_needed_for_dialog_rename: false + +# This allows custom anvil to not "guess" the text used for rename but store it in the item +# It will make item stackable only and only if it had used the same rename text +# +# For practical reason. this only work when dialog rename is enabled +dialog_rename_keep_user_text: true # Override limits for specific enchants # @@ -83,7 +125,8 @@ default_limit: 5 # # Overrides provided default from aqua_affinity to depth_strider won't change effect with extra levels # -# Valid range of 1 - 255 for each enchantment +# Valid range of 0 - 255 for each enchantment +# -1 mean keep default enchant_limits: minecraft:aqua_affinity: 1 minecraft:binding_curse: 1 @@ -265,11 +308,20 @@ enchant_values: # Even if disable-merge-over of unbreaking is set to 2 # -1 mean enchantment merge for this enchantment is not disabled. default to -1 if absent. disable-merge-over: - # Sharpness is set to -1. it equivalent to it not being set to anything (and work as vanilla) + # Sharpness is set to -1. it equivalent to it not being set to anything (and work as vanilla on default configuration) minecraft:sharpness: -1 # If uncommented. 2 unbreaking II book would not give an unbreaking III book. but unbreaking III book can still be applied #minecraft:unbreaking: 2 +# The maximum number of enchantment an item can get. -1 for infinity +# Use eco enchant enchant_limit if present by default unless "default" is not equal to -1 +enchantment_count_limit: + default: -1 + # Limit for specific items. example bellow is an example with stick + # Per item enchantment limit override eco enchant enchant_limit and default limit + items: + stick: -1 + # Settings for lore modification lore_edit: book_and_quil: @@ -292,9 +344,12 @@ lore_edit: # # Color code are prefixed by "&" and hexadecimal color by "#" # Color code will not be applied if it colors nothing. "&&" can be used to write "&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + # + # Note that currently minimessage would disable hex code when adding color allow_color_code: true - allow_hexadecimal_color: true - use_cost: 0 + allow_hexadecimal_color: false + allow_minimessage: true remove: # If removing lore using book & quil is enabled @@ -309,16 +364,25 @@ lore_edit: shared_additive: false # If removing the lore consume the book & quil do_consume: false - # If the color should get back to color code or hex format - remove_color_on_remove: true # Cost of replacing colors remove_color_cost: 0 + # Allowed some color and tags to be reverted to plain text + # Custom anvil will prioritise format that result is a smaller resulting text + # Note that not allowing certain format will lead to some lost of color or tags. + # If configuration are exact as append appending this book should result in the exact same color + # + # Color code will be prefixed by "&" and hexadecimal color by "#". + # If color code is allowed, "&" in the text will get converted to "&&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + allow_color_code: true + allow_hexadecimal_color: false + allow_minimessage: true paper: # Permission is ca.lore_edit.paper use_permission: true # what order should the lines should get added/removed (start/end, if invalid or not present will be end) - order: "end" + order: end append_line: # If adding lore line using paper is enabled @@ -335,8 +399,12 @@ lore_edit: # # Color code are prefixed by "&" and hexadecimal color by "#" # Color code will not be applied if it colors nothing. "&&" can be used to write "&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + # + # Note that currently minimessage would disable hex code when adding color allow_color_code: true - allow_hexadecimal_color: true + allow_hexadecimal_color: false + allow_minimessage: true color_use_cost: 0 remove_line: @@ -350,10 +418,47 @@ lore_edit: shared_additive: false # If removing the lore line consume the paper do_consume: false - # If the color should get back to color code or hex format - remove_color_on_remove: true # Cost of replacing colors remove_color_cost: 0 + # Allowed some color and tags to be reverted to plain text + # Custom anvil will prioritise format that result is a smaller resulting text + # Note that not allowing certain format will lead to some lost of color or tags. + # If configuration are exact as append appending this paper should result in the exact same color + # + # Color code will be prefixed by "&" and hexadecimal color by "#". + # If color code is allowed, "&" in the text will get converted to "&&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + allow_color_code: true + allow_hexadecimal_color: false + allow_minimessage: true + +# Allow to replace the xp cost by a monetary cost +# If enabled it will not be bound to the experience level limits +# +# It also requires to enable dialog rename (set "enable_dialog_rename: false" a bit higher) +# If dialog rename permission is enabled and player do not have the permission merge will fall back to vanilla xp cost +# +# If you are using custom craft I recommend using Linear Xp Cost with Exact Linear Xp as normal Xp Cost will act "weird" +# But Linear Xp will act as 1$ time global multiplier. In other word: like you expect +# +# As this feature require dialog rename, it can only be enabled starting with paper 1.21.6 and later +monetary_cost: + enabled: false + # If using vault unlocked this allow to specify what currency should be used for anvil usage + # default being the default currency + currency: default + # multiply the anvil cost by a value to allow to have price a big bigger than like 40 + multipliers: + # global multipliers. all usage type will be multiplied by this value + global: 1.0 + # usage specific type. it will only apply for specific xp "reason" + enchantment: 1.0 # related to enchantments level + repair: 1.0 # for repairing via unit repair (per unit) + rename: 1.0 # for renaming the item + lore_edit: 1.0 # for changing the lore of the item (only if lore edit is enabled) + illegal_penalty: 1.0 # for trying to combine illegal enchantment + work_penalty: 1.0 # for work penalty (aka use penalty) + recipe: 1.0 # for custom anvil recipe cost # Whether to show debug logging debug_log: false @@ -361,10 +466,4 @@ debug_log: false # Whether to show verbose debug logging debug_log_verbose: false -# In case something when wrong with CustomAnvil packet manager. -# If you see "missing class exception" or similar you may test this. -# If enabled and Protocolib absent or disabled "Replace to expensive" will not work. -# ProtocoLib may also be used if the server is in an "unsupported" version even if this option is disabled. -force_protocolib: false - -configVersion: 1.8.0 +configVersion: 1.11.0 diff --git a/src/main/resources/datapack/bracken/enchant_conflict.yml b/src/main/resources/datapack/bracken/enchant_conflict.yml new file mode 100644 index 0000000..851a398 --- /dev/null +++ b/src/main/resources/datapack/bracken/enchant_conflict.yml @@ -0,0 +1,35 @@ +"bracken:abstraction": ['#sword_enchant_conflict'] +"bracken:antivenom": ['#protection_enchant_conflict'] +"bracken:blinding_fix": ['minecraft:fire_aspect'] +"bracken:boldness": ['#protection_enchant_conflict'] +"bracken:butchering": ['#sword_enchant_conflict'] +"bracken:defusing": ['#sword_enchant_conflict'] +"bracken:dentine_touch": ['#tool_conflict'] +"bracken:dullness_curse": ['minecraft:sharpness'] +"bracken:famine_walker": ['#boot_conflict'] +"bracken:ferocity": ['#sword_enchant_conflict'] +"bracken:flame_walker": ['#boot_conflict'] +"bracken:float_walker": ['#boot_conflict'] +"bracken:flood_walker": ['#boot_conflict'] +"bracken:fragility_curse": ['minecraft:unbreaking'] +"bracken:freezing_fix": ['minecraft:fire_aspect'] +"bracken:guarding": ['#sword_enchant_conflict'] +"bracken:huskiness": ['#bracken_pet_armor'] +"bracken:infused_fire_aspect_fix": ['minecraft:fire_aspect'] +"bracken:infused_frost_walker_fix": ['#boot_conflict'] +"bracken:infused_mending_fix": ['minecraft:mending'] +"bracken:infused_thorns_fix": ['minecraft:thorns'] +"bracken:lethargy_curse": ['bracken:ferocity'] +"bracken:lifesteal": ['#bow_conflict'] +"bracken:litheness": ['#bracken_pet_armor'] +"bracken:lunar_concordance_armor": ['#bracken_lunarite_armor'] +"bracken:lunar_concordance_weapon": ['#bracken_lunarite_weapons'] +"bracken:maiming_curse": ['bracken:vitality_fix'] +"bracken:mortality": ['#sword_enchant_conflict'] +"bracken:poisoning_fix": ['minecraft:fire_aspect'] +"bracken:pugnacity": ['#bracken_pet_armor'] +"bracken:rending": ['#crossbow_conflict'] +"bracken:silvered_fix": ['#all'] +"bracken:vitality_fix": ['bracken:maiming_curse'] +"bracken:weakening_fix": ['minecraft:fire_aspect'] +"bracken:withering_fix": ['minecraft:fire_aspect'] diff --git a/src/main/resources/datapack/bracken/item_conflict.yml b/src/main/resources/datapack/bracken/item_conflict.yml new file mode 100644 index 0000000..1c4d9da --- /dev/null +++ b/src/main/resources/datapack/bracken/item_conflict.yml @@ -0,0 +1,54 @@ +"bracken:abstraction": ['melee_weapons', 'mace'] +"bracken:antivenom": ['armors'] +"bracken:astuteness_fix": ['armors'] +"bracken:blinding_fix": ['can_unbreak'] +"bracken:boldness": ['armors'] +"bracken:butchering": ['melee_weapons', 'mace'] +"bracken:chiseling_fix": ['axes', 'pickaxes', 'shovels', 'hoes'] +"bracken:decaying_fix": ['can_unbreak'] +"bracken:defusing": ['melee_weapons', 'mace'] +"bracken:dentine_touch": ['axes', 'pickaxes', 'shovels', 'hoes'] +"bracken:devouring_curse": ['melee_weapons', 'mace'] +"bracken:dullness_curse": ['melee_weapons', 'mace'] +"bracken:famine_walker": ['boots'] +"bracken:ferocity": ['melee_weapons', 'mace'] +"bracken:flame_walker": ['boots'] +"bracken:float_walker": ['boots'] +"bracken:flood_walker": ['boots'] +"bracken:fragility_curse": ['can_unbreak'] +"bracken:freezing_fix": ['can_unbreak'] +"bracken:guarding": ['melee_weapons', 'mace'] +"bracken:huskiness": ['pet_armor'] +"bracken:infused_fire_aspect_fix": ['melee_weapons', 'mace'] +"bracken:infused_frost_walker_fix": ['boots'] +"bracken:infused_mending_fix": ['can_unbreak'] +"bracken:infused_thorns_fix": ['armors'] +"bracken:integrity_fix": ['armors'] +"bracken:lethargy_curse": ['melee_weapons', 'mace'] +"bracken:lifesteal": ['bow'] +"bracken:litheness": ['pet_armor'] +"bracken:maiming_curse": ['armors'] +"bracken:mortality": ['melee_weapons', 'mace'] +"bracken:poisoning_fix": ['can_unbreak'] +"bracken:pugnacity": ['wolf_armor'] +"bracken:pushback": ['armors'] +"bracken:quenching_fix": ['armors'] +"bracken:rending": ['crossbow'] +"bracken:reverse_thorns": ['axes'] +"bracken:searing_surface": ['armors'] +"bracken:sentience_curse_1": ['can_unbreak'] +"bracken:sentience_curse_2": ['can_unbreak'] +"bracken:sentience_curse_3": ['can_unbreak'] +"bracken:sentience_curse_4": ['can_unbreak'] +"bracken:sentience_curse_5": ['can_unbreak'] +"bracken:sentience_curse_6": ['can_unbreak'] +"bracken:sentience_curse_7": ['can_unbreak'] +"bracken:shrewdness": ['swords'] +"bracken:silvered_fix": ['can_unbreak'] +"bracken:spectrality_fix": ['melee_weapons', 'mace'] +"bracken:splintering": ['bow'] +"bracken:trampling": ['horse_armor'] +"bracken:vitality_fix": ['armors'] +"bracken:weakening_fix": ['can_unbreak'] +"bracken:wisdom": ['tools'] +"bracken:withering_fix": ['can_unbreak'] diff --git a/src/main/resources/datapack/bracken/item_groups.yml b/src/main/resources/datapack/bracken/item_groups.yml new file mode 100644 index 0000000..8248f0c --- /dev/null +++ b/src/main/resources/datapack/bracken/item_groups.yml @@ -0,0 +1,15 @@ +horse_armor: + items: + - leather_horse_armor + - iron_horse_armor + - golden_horse_armor + - diamond_horse_armor + +wolf_armor: + items: + - wolf_armor + +pet_armor: + groups: + - wolf_armor + - horse_armor \ No newline at end of file diff --git a/src/main/resources/datapack/dungeons_and_taverns/enchant_conflict.yml b/src/main/resources/datapack/dungeons_and_taverns/enchant_conflict.yml new file mode 100644 index 0000000..5ebd042 --- /dev/null +++ b/src/main/resources/datapack/dungeons_and_taverns/enchant_conflict.yml @@ -0,0 +1,21 @@ +"nova_structures:power": ['#dungeon_at_might', '#dungeon_at_powah'] +"nova_structures:ghasted": ['#crossbow_conflict'] +"nova_structures:gravity": ['#crossbow_conflict'] +"nova_structures:antidote": ['#protection_enchant_conflict'] +"nova_structures:piercing": ['#dungeon_at_structure_bow'] +"nova_structures:traveler": ['#dungeon_at_boots'] +"nova_structures:multishot": ['#dungeon_at_structure_bow'] +"nova_structures:wax_wings": ['#dungeon_at_elytra'] +"nova_structures:aerials_bane": ['#dungeon_at_powah', '#sword_enchant_conflict'] +"nova_structures:shulker_boss": ['minecraft:unbreaking'] +"nova_structures:illagers_bane": ['#sword_enchant_conflict'] +"nova_structures:wither_coated": ['#dungeon_at_sword_effect'] +"nova_structures:boss_behaviour": ['minecraft:unbreaking'] +"nova_structures:photosynthesis": ['#dungeon_at_repair'] +"nova_structures:shulker_miniboss": ['minecraft:unbreaking'] +"minecraft:soul_speed": ['#dungeon_at_boots'] +"minecraft:mending": ['#dungeon_at_repair'] +"minecraft:power": ['#dungeon_at_powah'] +"minecraft:unbreaking": ['#dungeon_at_elytra'] +"minecraft:infinity": ['#dungeon_at_structure_bow'] +"minecraft:fire_aspect": ['#dungeon_at_sword_effect'] diff --git a/src/main/resources/datapack/dungeons_and_taverns/item_conflict.yml b/src/main/resources/datapack/dungeons_and_taverns/item_conflict.yml new file mode 100644 index 0000000..d98c5dd --- /dev/null +++ b/src/main/resources/datapack/dungeons_and_taverns/item_conflict.yml @@ -0,0 +1,18 @@ +"nova_structures:power": ['crossbow'] +"nova_structures:ghasted": ['crossbow'] +"nova_structures:gravity": ['crossbow'] +"nova_structures:antidote": ['chestplate'] +"nova_structures:outreach": ['chestplate'] +"nova_structures:piercing": ['bow'] +"nova_structures:spiteful": ['melee_weapons'] +"nova_structures:traveler": ['boots'] +"nova_structures:multishot": ['bow'] +"nova_structures:wax_wings": ['elytra'] +"nova_structures:aerials_bane": ['combat_tools'] +"nova_structures:shulker_boss": ['stick'] +"nova_structures:illagers_bane": ['melee_weapons'] +"nova_structures:wither_coated": ['mace', 'melee_weapons'] +"nova_structures:boss_behaviour": ['stick', 'flint'] +"nova_structures:photosynthesis": ['can_unbreak'] +"nova_structures:shulker_miniboss": ['stick'] +"nova_structures:conductivity_curse": ['metal'] diff --git a/src/main/resources/datapack/dungeons_and_taverns/item_groups.yml b/src/main/resources/datapack/dungeons_and_taverns/item_groups.yml new file mode 100644 index 0000000..45fc3b7 --- /dev/null +++ b/src/main/resources/datapack/dungeons_and_taverns/item_groups.yml @@ -0,0 +1,56 @@ +metal: + type: include + items: + - shield + - trident + - flint_and_steel + - shears + - brush + - mace + - chainmail_chestplate + - golden_chestplate + - iron_chestplate + - netherite_chestplate + - chainmail_boots + - golden_boots + - iron_boots + - netherite_boots + - chainmail_leggings + - golden_leggings + - iron_leggings + - netherite_leggings + - chainmail_helmet + - golden_helmet + - iron_helmet + - netherite_helmet + - golden_pickaxe + - iron_pickaxe + - netherite_pickaxe + - golden_axe + - iron_axe + - netherite_axe + - golden_shovel + - iron_shovel + - netherite_shovel + - golden_sword + - iron_sword + - netherite_sword + - golden_hoe + - iron_hoe + - netherite_hoe + - trial_key + - ominous_trial_key + +combat_tools: + type: include + groups: + - melee_weapons + - bow + - crossbow + - trident + - mace + +flint: + type: include + items: + - flint \ No newline at end of file diff --git a/src/main/resources/datapack/enchantplus/enchant_conflict.yml b/src/main/resources/datapack/enchantplus/enchant_conflict.yml new file mode 100644 index 0000000..a8e3e61 --- /dev/null +++ b/src/main/resources/datapack/enchantplus/enchant_conflict.yml @@ -0,0 +1,40 @@ +"enchantplus:bow/echo_shot": ['#enchantplus_bow'] +"enchantplus:bow/storm_arrow": ['#enchantplus_bow'] +"enchantplus:bow/eternal_frost": ['#enchantplus_bow'] +"enchantplus:bow/breezing_arrow": ['#enchantplus_bow'] +"enchantplus:bow/explosive_arrow": ['#enchantplus_bow'] +"enchantplus:mace/teluric_wave": ['#enchantplus_mace'] +"enchantplus:armor/fury": ['#enchantplus_armor'] +"enchantplus:armor/lifeplus": ['#enchantplus_armor'] +"enchantplus:armor/venom_protection": ['#protection_enchant_conflict'] +"enchantplus:boots/lava_walker": ['#boot_conflict'] +"enchantplus:boots/step_assist": ['#boot_conflict'] +"enchantplus:sword/fear": ['#enchantplus_sword_effect'] +"enchantplus:sword/pull": ['#enchantplus_sword_effect'] +"enchantplus:sword/reach": ['#enchantplus_sword_attribute'] +"enchantplus:sword/critical": ['#enchantplus_sword_attribute'] +"enchantplus:sword/xp_boost": ['#enchantplus_experience'] +"enchantplus:sword/last_hope": ['#enchantplus_sword_effect'] +"enchantplus:sword/life_steal": ['#enchantplus_sword_effect'] +"enchantplus:sword/death_touch": ['#enchantplus_sword_effect'] +"enchantplus:sword/attack_speed": ['#enchantplus_sword_attribute'] +"enchantplus:sword/poison_aspect": ['#enchantplus_aspect'] +"enchantplus:sword/runic_despair": ['#enchantplus_sword_attribute'] +"enchantplus:sword/dimensional_hit": ['#enchantplus_sword_attribute'] +"enchantplus:sword/tears_of_asflors": ['#enchantplus_sword_effect'] +"enchantplus:tools/auto_smelt": ['minecraft:silk_touch'] +"enchantplus:tools/miningplus": ['#enchantplus_mining'] +"enchantplus:pickaxe/vein_miner": ['#enchantplus_mining'] +"enchantplus:pickaxe/spawner_touch": ['#enchantplus_mining'] +"enchantplus:pickaxe/bedrock_breaker": ['#enchantplus_mining'] +"enchantplus:trident/gungnir_breath": ['#enchantplus_trident'] +"enchantplus:leggings/dwarfed": ['#enchantplus_size'] +"enchantplus:leggings/oversize": ['#enchantplus_size'] +"enchantplus:durability/curse_of_enchant": ['#enchantplus_durability', 'minecraft:unbreaking', 'minecraft:mending'] +"enchantplus:durability/curse_of_breaking": ['#enchantplus_durability', 'minecraft:unbreaking', 'minecraft:mending'] +"minecraft:protection": ['#enchantplus_armor'] +"minecraft:quick_charge": ['#enchantplus_bow'] +"minecraft:mending": ['#enchantplus_experience'] +"minecraft:fire_aspect": ['#enchantplus_aspect'] +"minecraft:channeling": ['#enchantplus_trident'] +"minecraft:wind_burst": ['#enchantplus_mace'] diff --git a/src/main/resources/datapack/enchantplus/item_conflict.yml b/src/main/resources/datapack/enchantplus/item_conflict.yml new file mode 100644 index 0000000..8dafc57 --- /dev/null +++ b/src/main/resources/datapack/enchantplus/item_conflict.yml @@ -0,0 +1,55 @@ +"enchantplus:axe/timber": ['axes'] +"enchantplus:bow/rebound": ['bow', 'crossbow'] +"enchantplus:bow/echo_shot": ['bow', 'crossbow'] +"enchantplus:bow/storm_arrow": ['bow', 'crossbow'] +"enchantplus:bow/accuracy_shot": ['bow', 'crossbow'] +"enchantplus:bow/eternal_frost": ['bow', 'crossbow'] +"enchantplus:bow/breezing_arrow": ['bow', 'crossbow'] +"enchantplus:bow/explosive_arrow": ['bow', 'crossbow'] +"enchantplus:hoe/harvest": ['hoes'] +"enchantplus:hoe/scyther": ['hoes'] +"enchantplus:mace/striker": ['mace'] +"enchantplus:mace/teluric_wave": ['mace'] +"enchantplus:mace/wind_propulsion": ['mace'] +"enchantplus:armor/fury": ['armors'] +"enchantplus:armor/lifeplus": ['armors'] +"enchantplus:armor/venom_protection": ['armors'] +"enchantplus:boots/agility": ['boots'] +"enchantplus:boots/lava_walker": ['boots'] +"enchantplus:boots/step_assist": ['boots'] +"enchantplus:sword/fear": ['swords'] +"enchantplus:sword/pull": ['melee_weapons'] +"enchantplus:sword/reach": ['swords'] +"enchantplus:sword/critical": ['swords'] +"enchantplus:sword/xp_boost": ['mining_and_damage'] +"enchantplus:sword/last_hope": ['swords'] +"enchantplus:sword/life_steal": ['melee_weapons'] +"enchantplus:sword/death_touch": ['melee_weapons'] +"enchantplus:sword/attack_speed": ['swords'] +"enchantplus:sword/poison_aspect": ['melee_weapons'] +"enchantplus:sword/runic_despair": ['swords'] +"enchantplus:sword/dimensional_hit": ['swords'] +"enchantplus:sword/tears_of_asflors": ['swords'] +"enchantplus:tools/auto_smelt": ['axes', 'pickaxes', 'shovels', 'hoes'] +"enchantplus:tools/miningplus": ['axes', 'pickaxes', 'shovels', 'hoes'] +"enchantplus:elytra/armored": ['elytra'] +"enchantplus:elytra/kinetic_protection": ['elytra'] +"enchantplus:helmet/voidless": ['helmets'] +"enchantplus:helmet/auto_feed": ['helmets'] +"enchantplus:helmet/bright_vision": ['helmets'] +"enchantplus:mounted/velocity": ['mounted_armor'] +"enchantplus:mounted/steel_fang": ['wolf_armor'] +"enchantplus:mounted/cavalier_egis": ['mounted_armor'] +"enchantplus:mounted/ethereal_leap": ['mounted_armor'] +"enchantplus:pickaxe/vein_miner": ['pickaxes'] +"enchantplus:pickaxe/spawner_touch": ['pickaxes'] +"enchantplus:pickaxe/bedrock_breaker": ['pickaxes'] +"enchantplus:trident/gungnir_breath": ['trident'] +"enchantplus:leggings/dwarfed": ['leggings'] +"enchantplus:leggings/leaping": ['leggings'] +"enchantplus:leggings/oversize": ['leggings'] +"enchantplus:leggings/fast_swim": ['leggings'] +"enchantplus:chestplate/builder_arm": ['chestplate'] +"enchantplus:durability/curse_of_enchant": ['can_unbreak'] +"enchantplus:durability/curse_of_breaking": ['can_unbreak'] +"enchantplus:midas_touch": ['stick'] diff --git a/src/main/resources/datapack/enchantplus/item_groups.yml b/src/main/resources/datapack/enchantplus/item_groups.yml new file mode 100644 index 0000000..74854de --- /dev/null +++ b/src/main/resources/datapack/enchantplus/item_groups.yml @@ -0,0 +1,30 @@ +mining_and_damage: + groups: + - melee_weapons + - mace + - bow + - crossbow + - mace + - trident + - tools + +stick: + items: + - stick + +wolf_armor: + items: + - wolf_armor + +mounted_armor: + items: + - diamond_horse_armor + - golden_horse_armor + - iron_horse_armor + - leather_horse_armor + groups: + - wolf_armor + +elytra: + items: + - elytra \ No newline at end of file diff --git a/src/main/resources/enchant_conflict.yml b/src/main/resources/enchant_conflict.yml index 0e8b3f3..45d62c3 100644 --- a/src/main/resources/enchant_conflict.yml +++ b/src/main/resources/enchant_conflict.yml @@ -92,6 +92,10 @@ restriction_loyalty: enchantments: [ minecraft:loyalty ] notAffectedGroups: [ enchanted_book, trident ] +restriction_luck_of_the_sea: + enchantments: [ minecraft:luck_of_the_sea ] + notAffectedGroups: [ enchanted_book, fishing_rod ] + restriction_lure: enchantments: [ minecraft:lure ] notAffectedGroups: [ enchanted_book, fishing_rod ] diff --git a/src/main/resources/item_groups.yml b/src/main/resources/item_groups.yml index 3c1eb5d..8a62f3d 100644 --- a/src/main/resources/item_groups.yml +++ b/src/main/resources/item_groups.yml @@ -126,7 +126,7 @@ wearable: groups: - armors -tools: +pickaxes: type: include items: - wooden_pickaxe @@ -135,19 +135,33 @@ tools: - diamond_pickaxe - golden_pickaxe - netherite_pickaxe + +shovels: + type: include + items: - wooden_shovel - stone_shovel - iron_shovel - diamond_shovel - golden_shovel - netherite_shovel + +hoes: + type: include + items: - wooden_hoe - stone_hoe - iron_hoe - diamond_hoe - golden_hoe - netherite_hoe + +tools: + type: include groups: + - pickaxes + - shovels + - hoes - axes enchanted_book: diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 0e58169..dab9997 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -10,6 +10,10 @@ authors: [ DelilahEve, alexcrea ] libraries: [${libraries}] commands: + customanvil: + description: Generic command for custom anvil + aliases: + - ca anvilconfigreload: description: Reload every config of this plugin permission: ca.command.reload @@ -36,6 +40,9 @@ permissions: ca.command.reload: default: op description: Allow administrator to reload the plugin's configs + ca.command.diagnostic: + default: op + description: Get debug information about the plugin and server ca.config.edit: default: op description: Allow administrator to edit the plugin's config in game @@ -46,6 +53,9 @@ permissions: ca.color.hex: default: op description: Allow player to use hexadecimal color if enabled (toggleable) + ca.rename.minimessage: + default: op + description: Allow player to use minimessage formating on rename if enabled (toggleable) (only legacy compatible at the time) # lore edit permissions ca.lore_edit.book: default: op @@ -53,6 +63,11 @@ permissions: ca.lore_edit.paper: default: op description: Allow player to edit lore via paper if enabled (toggleable) + # dialog menu permission + ca.rename.dialog: + default: op + description: Allow player to use the dialog rename menu (toggleable) + # soft depend on old name of this plugin (UnsafeEnchantsPlus), so I can disable it if it is on the same server # Also depend to other plugin for compatibility @@ -65,3 +80,4 @@ softdepend: - eco - ExcellentEnchants - HavenBags + - SuperEnchants diff --git a/src/test/java/io/delilaheve/CustomAnvilTests.java b/src/test/java/io/delilaheve/CustomAnvilTests.java index 45ecb7c..12e1ec1 100644 --- a/src/test/java/io/delilaheve/CustomAnvilTests.java +++ b/src/test/java/io/delilaheve/CustomAnvilTests.java @@ -8,11 +8,15 @@ public class CustomAnvilTests extends DefaultCustomAnvilTest { @Test public void simpleInitTest() { - Assertions.assertNotNull(server); - Assertions.assertNotNull(plugin); + try { + Assertions.assertNotNull(server); + Assertions.assertNotNull(plugin); - // Test shutdown - plugin.onDisable(); + // Test shutdown + plugin.onDisable(); + } catch (Exception e) { + Assertions.fail(e); + } } } diff --git a/src/test/java/io/delilaheve/util/EnchantmentUtilTests.java b/src/test/java/io/delilaheve/util/EnchantmentUtilTests.java index 48efc6b..10ea252 100644 --- a/src/test/java/io/delilaheve/util/EnchantmentUtilTests.java +++ b/src/test/java/io/delilaheve/util/EnchantmentUtilTests.java @@ -88,16 +88,16 @@ public class EnchantmentUtilTests extends ConfigResetCustomAnvilTest { ); // Test with no permission - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData2); + nullResultData.executeTest(anvil, player); + nullResultData2.executeTest(anvil, player); // Add permission PermissionAttachment attachment = player.addAttachment(plugin); attachment.setPermission(permission, true); // Test with new permission - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData2); + legalResultData.executeTest(anvil, player); + legalResultData2.executeTest(anvil, player); } @Test @@ -161,24 +161,24 @@ public class EnchantmentUtilTests extends ConfigResetCustomAnvilTest { ); // Test failing result first - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData2); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData3); + nullResultData2.executeTest(anvil, player); + nullResultData3.executeTest(anvil, player); // Test working sharpness 2 - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData); + legalResultData.executeTest(anvil, player); // Set merge limit to 2 & test ConfigHolder.DEFAULT_CONFIG.getConfig().set("disable-merge-over.minecraft:sharpness", 1); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData); + nullResultData.executeTest(anvil, player); // Add permission PermissionAttachment attachment = player.addAttachment(plugin); attachment.setPermission(permission, true); // Test working sharpness 2 - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData2); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData3); + legalResultData.executeTest(anvil, player); + legalResultData2.executeTest(anvil, player); + legalResultData3.executeTest(anvil, player); } } diff --git a/src/test/java/xyz/alexcrea/cuanvil/anvil/AnvilFuseTests.java b/src/test/java/xyz/alexcrea/cuanvil/anvil/AnvilFuseTests.java index 60df37e..19f1d6f 100644 --- a/src/test/java/xyz/alexcrea/cuanvil/anvil/AnvilFuseTests.java +++ b/src/test/java/xyz/alexcrea/cuanvil/anvil/AnvilFuseTests.java @@ -67,7 +67,7 @@ public class AnvilFuseTests extends SharedCustomAnvilTest { 5 ); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, data); + data.executeTest(anvil, player); } @Test @@ -87,7 +87,7 @@ public class AnvilFuseTests extends SharedCustomAnvilTest { 5 ); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, data); + data.executeTest(anvil, player); } @Test @@ -101,7 +101,7 @@ public class AnvilFuseTests extends SharedCustomAnvilTest { null ); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, data); + data.executeTest(anvil, player); } // Note: currently anvil can only have null name. maybe handle differently later @@ -117,10 +117,10 @@ public class AnvilFuseTests extends SharedCustomAnvilTest { AnvilFuseTestData data = new AnvilFuseTestData( base, null, expected, expected, null, - 1, 1, null + 1, null, 1 ); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, data); + data.executeTest(anvil, player); } } diff --git a/src/test/java/xyz/alexcrea/cuanvil/anvil/LoreEditTests.java b/src/test/java/xyz/alexcrea/cuanvil/anvil/LoreEditTests.java index 4465be4..668da58 100644 --- a/src/test/java/xyz/alexcrea/cuanvil/anvil/LoreEditTests.java +++ b/src/test/java/xyz/alexcrea/cuanvil/anvil/LoreEditTests.java @@ -29,9 +29,11 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +// TODO redo test as now color split should be handled in AnvilColorUtilTest and not here +// Especially since some behavior changed public class LoreEditTests extends SharedCustomAnvilTest { - private static AnvilInventory anvil; + /*private static AnvilInventory anvil; private static PlayerMock player; private static final String COLORED_LORE_LINE = "§x§1§2§3§4§5§6TEST §atest"; @@ -289,9 +291,9 @@ public class LoreEditTests extends SharedCustomAnvilTest { if (type.isAppend()) { ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.ALLOW_HEX_COLOR, true); ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.ALLOW_COLOR_CODE, true); + ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.ALLOW_MINIMESSAGE, true); ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.USE_COLOR_COST, 0); } else { - ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.REMOVE_COLOR_ON_LORE_REMOVE, false); ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.REMOVE_COLOR_COST, 0); } @@ -362,13 +364,13 @@ public class LoreEditTests extends SharedCustomAnvilTest { return typeList; } - @Test - public void simpleTest() { + @ParameterizedTest + @EnumSource(LoreEditType.class) + public void simpleTest(LoreEditType type) { // Test all defaults to make sure they works - for (LoreEditType type : LoreEditType.values()) { - singleLineTypeToTest.get(type).executeTest(anvil, player); - multiLineTypeToTest.get(type).executeTest(anvil, player); - } + singleLineTypeToTest.get(type).executeTest(anvil, player); + multiLineTypeToTest.get(type).executeTest(anvil, player); + } @ParameterizedTest @@ -437,7 +439,6 @@ public class LoreEditTests extends SharedCustomAnvilTest { public void testColorCost(LoreEditType type) { ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.USE_COLOR_COST, COLOR_USE_COST); ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.REMOVE_COLOR_COST, COLOR_REMOVE_COST); - ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.REMOVE_COLOR_ON_LORE_REMOVE, true); TestDataContainer singleLData = singleLineTypeToTest.get(type); TestDataContainer multiLData = multiLineTypeToTest.get(type); @@ -481,8 +482,6 @@ public class LoreEditTests extends SharedCustomAnvilTest { @ParameterizedTest @MethodSource("onlyRemoveTypes") public void testColorRemoveEnabled(LoreEditType type) { - ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.REMOVE_COLOR_ON_LORE_REMOVE, true); - TestDataContainer singleLData = singleLineTypeToTest.get(type); TestDataContainer multiLData = multiLineTypeToTest.get(type); @@ -618,6 +617,6 @@ public class LoreEditTests extends SharedCustomAnvilTest { ).executeTest(anvil, player); } - //TODO work penalty test + //TODO work penalty test*/ } diff --git a/src/test/java/xyz/alexcrea/cuanvil/api/AnvilRecipeBuilderTest.java b/src/test/java/xyz/alexcrea/cuanvil/api/AnvilRecipeBuilderTest.java index 2b57bda..d9ff329 100644 --- a/src/test/java/xyz/alexcrea/cuanvil/api/AnvilRecipeBuilderTest.java +++ b/src/test/java/xyz/alexcrea/cuanvil/api/AnvilRecipeBuilderTest.java @@ -12,10 +12,15 @@ import static org.junit.jupiter.api.Assertions.*; public class AnvilRecipeBuilderTest extends SharedOnlyMockBukkit { private AnvilRecipeBuilder builder; + private AnvilRecipeBuilder builder2; @BeforeEach public void setup() { builder = new AnvilRecipeBuilder("test"); + builder2 = new AnvilRecipeBuilder("test"); + + builder2.setLeftItem(new ItemStack(Material.STICK)); + builder2.setResultItem(new ItemStack(Material.STICK)); } @Test @@ -38,6 +43,7 @@ public class AnvilRecipeBuilderTest extends SharedOnlyMockBukkit { .setResultItem(new ItemStack(Material.STICK)); assertNotNull(builder.build()); + assertNotNull(builder2.build()); } @Test @@ -63,23 +69,39 @@ public class AnvilRecipeBuilderTest extends SharedOnlyMockBukkit { @Test void setXpCostPerCraft(){ - assertEquals(1, builder.getXpCostPerCraft()); - builder.setXpCostPerCraft(2); - assertEquals(2, builder.getXpCostPerCraft()); + assertEquals(0, builder2.getLevelCostPerCraft()); + assertEquals(0, builder2.build().getLevelCostPerCraft()); + builder2.setLevelCostPerCraft(2); + assertEquals(2, builder2.getLevelCostPerCraft()); + assertEquals(2, builder2.build().getLevelCostPerCraft()); } + @Test + void setLinearXpCostPerCraft(){ + assertEquals(0, builder2.getLinearXpCostPerCraft()); + assertEquals(0, builder2.build().getXpCostPerCraft()); + builder2.setLinearXpCostPerCraft(2); + assertEquals(2, builder2.getLinearXpCostPerCraft()); + assertEquals(2, builder2.build().getXpCostPerCraft()); + } + + @Test void setExactCount(){ - assertTrue(builder.isExactCount()); - builder.setExactCount(false); - assertFalse(builder.isExactCount()); + assertTrue(builder2.isExactCount()); + assertTrue(builder2.build().getExactCount()); + builder2.setExactCount(false); + assertFalse(builder2.isExactCount()); + assertFalse(builder2.build().getExactCount()); } @Test void setName(){ - assertEquals("test", builder.getName()); - builder.setName("other"); - assertEquals("other", builder.getName()); + assertEquals("test", builder2.getName()); + assertEquals("test", builder2.build().getName()); + builder2.setName("other"); + assertEquals("other", builder2.getName()); + assertEquals("other", builder2.build().getName()); } } diff --git a/src/test/java/xyz/alexcrea/cuanvil/api/ConflictApiTests.java b/src/test/java/xyz/alexcrea/cuanvil/api/ConflictApiTests.java index b1d7564..1a9a256 100644 --- a/src/test/java/xyz/alexcrea/cuanvil/api/ConflictApiTests.java +++ b/src/test/java/xyz/alexcrea/cuanvil/api/ConflictApiTests.java @@ -71,7 +71,7 @@ public class ConflictApiTests extends ConfigResetCustomAnvilTest { Assertions.assertNotNull(sharpness); // Testing default conflict (illegal item should not be produced) - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData); + nullResultData.executeTest(anvil, player); // Try to find & remove conflict EnchantConflictGroup conflict = findGroup("sword_enchant_conflict"); @@ -79,7 +79,7 @@ public class ConflictApiTests extends ConfigResetCustomAnvilTest { // Test what happen when we remove the conflict (illegal item should be allowed) ConflictAPI.removeConflict(conflict); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData); + legalResultData.executeTest(anvil, player); // We create and add a new conflict ConflictBuilder builder = new ConflictBuilder("sword_enchant_conflict"); @@ -88,11 +88,11 @@ public class ConflictApiTests extends ConfigResetCustomAnvilTest { // Nothing should change as it is not new: it was previously deleted Assertions.assertFalse(builder.registerIfNew()); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData); + legalResultData.executeTest(anvil, player); // Now the conflict should be registered and conflict should exist Assertions.assertTrue(builder.registerIfAbsent()); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData); + nullResultData.executeTest(anvil, player); } @Test diff --git a/src/test/java/xyz/alexcrea/cuanvil/api/CustomAnvilRecipeApiTests.java b/src/test/java/xyz/alexcrea/cuanvil/api/CustomAnvilRecipeApiTests.java index 3ebdd7c..651b873 100644 --- a/src/test/java/xyz/alexcrea/cuanvil/api/CustomAnvilRecipeApiTests.java +++ b/src/test/java/xyz/alexcrea/cuanvil/api/CustomAnvilRecipeApiTests.java @@ -1,6 +1,7 @@ package xyz.alexcrea.cuanvil.api; import org.bukkit.Material; +import org.bukkit.event.Event; import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.AnvilInventory; import org.bukkit.inventory.Inventory; @@ -11,10 +12,11 @@ import org.junit.jupiter.api.Test; import org.mockbukkit.mockbukkit.entity.PlayerMock; import org.mockbukkit.mockbukkit.inventory.ItemStackMock; import xyz.alexcrea.cuanvil.config.ConfigHolder; +import xyz.alexcrea.cuanvil.data.AnvilClickTestData; +import xyz.alexcrea.cuanvil.data.TestDataContainer; import xyz.alexcrea.cuanvil.recipe.AnvilCustomRecipe; import xyz.alexcrea.cuanvil.tests.ConfigResetCustomAnvilTest; import xyz.alexcrea.cuanvil.data.AnvilFuseTestData; -import xyz.alexcrea.cuanvil.util.AnvilFuseTestUtil; import static org.junit.jupiter.api.Assertions.*; @@ -57,14 +59,14 @@ public class CustomAnvilRecipeApiTests extends ConfigResetCustomAnvilTest { ); // Testing default conflict (no recipe exist) - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData); + nullResultData.executeTest(anvil, player); // Add and test recipe AnvilRecipeBuilder builder = new AnvilRecipeBuilder(recipeName); - builder.setExactCount(true).setLeftItem(stick).setResultItem(stick).setXpCostPerCraft(2); + builder.setExactCount(true).setLeftItem(stick).setResultItem(stick).setLevelCostPerCraft(2); assertTrue(builder.registerIfAbsent()); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData); + legalResultData.executeTest(anvil, player); AnvilCustomRecipe recipe = getByName(recipeName); assertNotNull(recipe); @@ -72,21 +74,21 @@ public class CustomAnvilRecipeApiTests extends ConfigResetCustomAnvilTest { // Remove recipe assertTrue(CustomAnvilRecipeApi.removeRecipe(recipe)); assertFalse(CustomAnvilRecipeApi.removeRecipe(recipe)); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData); + nullResultData.executeTest(anvil, player); recipe = getByName(recipeName); assertNull(recipe); // Try to add deleted recipe with no override (should not add) assertFalse(CustomAnvilRecipeApi.addRecipe(builder, false)); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData); + nullResultData.executeTest(anvil, player); recipe = getByName(recipeName); assertNull(recipe); // Try to add deleted recipe with override (should add) assertTrue(CustomAnvilRecipeApi.addRecipe(builder, true)); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData); + legalResultData.executeTest(anvil, player); recipe = getByName(recipeName); assertNotNull(recipe); @@ -119,25 +121,134 @@ public class CustomAnvilRecipeApiTests extends ConfigResetCustomAnvilTest { null, null ); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData); + nullResultData.executeTest(anvil, player); AnvilRecipeBuilder builder = new AnvilRecipeBuilder(recipeName); builder.setExactCount(false) .setLeftItem(stick) .setResultItem(stick2) - .setXpCostPerCraft(2); + .setLevelCostPerCraft(2); assertTrue(builder.registerIfAbsent()); // Now working test - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData1); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData2); + legalResultData1.executeTest(anvil, player); + legalResultData2.executeTest(anvil, player); + } + + @Test + public void testLinearXpCost() { + String recipeName = "stick_recipe"; + ItemStack stick = new ItemStackMock(Material.STICK); + ItemStack stick2 = new ItemStackMock(Material.STICK, 2); + ItemStack stick5 = new ItemStackMock(Material.STICK, 5); + ItemStack stick10 = new ItemStackMock(Material.STICK, 10); + + + AnvilFuseTestData nullResultData = new AnvilFuseTestData( + stick, stick, + null + ); + + TestDataContainer legalResultData1 = new TestDataContainer(new AnvilFuseTestData( + stick, stick, + null, stick2, null, + 1, + null, null + ), new AnvilClickTestData( + null, null, null, stick2, + 1, + Event.Result.DENY, true, Event.Result.DENY + + )); + + TestDataContainer legalResultData2 = new TestDataContainer(new AnvilFuseTestData( + stick5, stick, + null, stick10, null, + 4, + null, null + ), new AnvilClickTestData( + null, null, null, stick10, + 4, + Event.Result.DENY, true, Event.Result.DENY + + )); + + nullResultData.executeTest(anvil, player); + + AnvilRecipeBuilder builder = new AnvilRecipeBuilder(recipeName); + builder.setExactCount(false) + .setLeftItem(stick) + .setResultItem(stick2) + .setLevelCostPerCraft(0) + .setLinearXpCostPerCraft(10); + + assertTrue(builder.registerIfAbsent()); + + // Now working test + legalResultData1.executeTest(anvil, player); + legalResultData2.executeTest(anvil, player); + } + + @Test + public void testLinearXpCostRemoveExact() { + String recipeName = "stick_recipe"; + ItemStack stick = new ItemStackMock(Material.STICK); + ItemStack stick2 = new ItemStackMock(Material.STICK, 2); + ItemStack stick5 = new ItemStackMock(Material.STICK, 5); + ItemStack stick10 = new ItemStackMock(Material.STICK, 10); + + AnvilFuseTestData nullResultData = new AnvilFuseTestData( + stick, stick, + null + ); + + TestDataContainer legalResultData1 = new TestDataContainer(new AnvilFuseTestData( + stick, stick, + null, stick2, null, + 2, + null, null + ), new AnvilClickTestData( + null, null, null, stick2, + 2, + Event.Result.DENY, true, Event.Result.DENY + + )); + + TestDataContainer legalResultData2 = new TestDataContainer(new AnvilFuseTestData( + stick5, stick, + null, stick10, null, + 5, + null, null + ), new AnvilClickTestData( + null, null, null, stick10, + 5, + Event.Result.DENY, true, Event.Result.DENY + )); + + nullResultData.executeTest(anvil, player); + + AnvilRecipeBuilder builder = new AnvilRecipeBuilder(recipeName); + builder.setExactCount(false) + .setLeftItem(stick) + .setResultItem(stick2) + .setLinearXpCostPerCraft(10) + .setRemoveExactLinearXp(true); + + assertTrue(builder.registerIfAbsent()); + + // Now working test + legalResultData1.executeTest(anvil, player); + //TODO check exp ? + System.out.printf(String.valueOf(player.getExp())); + legalResultData2.executeTest(anvil, player); + //TODO check exp ? } @Nullable - public static AnvilCustomRecipe getByName(String name){ + public static AnvilCustomRecipe getByName(String name) { for (AnvilCustomRecipe registeredRecipe : CustomAnvilRecipeApi.getRegisteredRecipes()) { - if(registeredRecipe.getName().contentEquals(name)){ + if (registeredRecipe.getName().contentEquals(name)) { return registeredRecipe; } } diff --git a/src/test/java/xyz/alexcrea/cuanvil/api/MaterialGroupApiTests.java b/src/test/java/xyz/alexcrea/cuanvil/api/MaterialGroupApiTests.java index 164bc58..3fcafe7 100644 --- a/src/test/java/xyz/alexcrea/cuanvil/api/MaterialGroupApiTests.java +++ b/src/test/java/xyz/alexcrea/cuanvil/api/MaterialGroupApiTests.java @@ -15,7 +15,7 @@ public class MaterialGroupApiTests extends ConfigResetCustomAnvilTest { void groupAddAndRemove() { String groupName = "group"; IncludeGroup group = new IncludeGroup(groupName); - group.addToPolicy(Material.DIAMOND_PICKAXE); // We do not want it to be empty + group.addToPolicy(Material.DIAMOND_PICKAXE.getKey()); // We do not want it to be empty // Group not being set should not exist assertFalse(doGroupExist(groupName)); @@ -48,7 +48,7 @@ public class MaterialGroupApiTests extends ConfigResetCustomAnvilTest { void writeGroup_Reload() { String groupName = "group"; IncludeGroup group = new IncludeGroup(groupName); - group.addToPolicy(Material.DIAMOND_PICKAXE); // We do not want it to be empty + group.addToPolicy(Material.DIAMOND_PICKAXE.getKey()); // We do not want it to be empty // Group not being set should not exist assertFalse(doGroupExist(groupName)); diff --git a/src/test/java/xyz/alexcrea/cuanvil/api/UnitRepairApiTests.java b/src/test/java/xyz/alexcrea/cuanvil/api/UnitRepairApiTests.java index 8166797..adda4e1 100644 --- a/src/test/java/xyz/alexcrea/cuanvil/api/UnitRepairApiTests.java +++ b/src/test/java/xyz/alexcrea/cuanvil/api/UnitRepairApiTests.java @@ -58,7 +58,7 @@ public class UnitRepairApiTests extends ConfigResetCustomAnvilTest { 2 ); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData); + legalResultData.executeTest(anvil, player); } @Test @@ -76,7 +76,7 @@ public class UnitRepairApiTests extends ConfigResetCustomAnvilTest { // Remove unit repair assertTrue(UnitRepairApi.removeUnitRepair(Material.DIAMOND, Material.DIAMOND_PICKAXE)); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData); + nullResultData.executeTest(anvil, player); // see override assertFalse(UnitRepairApi.addUnitRepair(Material.DIAMOND, Material.DIAMOND_PICKAXE, 0.25)); @@ -107,12 +107,12 @@ public class UnitRepairApiTests extends ConfigResetCustomAnvilTest { 2 ); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData); + nullResultData.executeTest(anvil, player); // Add unit repair assertTrue(UnitRepairApi.addUnitRepair(Material.STICK, Material.DIAMOND_PICKAXE)); assertFalse(UnitRepairApi.addUnitRepair(Material.STICK, Material.DIAMOND_PICKAXE)); - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData); + legalResultData.executeTest(anvil, player); } } diff --git a/src/test/java/xyz/alexcrea/cuanvil/api/event/CAConfigReadyEventTest.java b/src/test/java/xyz/alexcrea/cuanvil/api/event/CAConfigReadyEventTest.java new file mode 100644 index 0000000..ff73a59 --- /dev/null +++ b/src/test/java/xyz/alexcrea/cuanvil/api/event/CAConfigReadyEventTest.java @@ -0,0 +1,49 @@ +package xyz.alexcrea.cuanvil.api.event; + +import io.delilaheve.CustomAnvil; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockbukkit.mockbukkit.MockBukkit; +import org.mockbukkit.mockbukkit.matcher.plugin.PluginManagerFiredEventClassMatcher; +import xyz.alexcrea.cuanvil.tests.SharedOnlyMockBukkit; + +public class CAConfigReadyEventTest extends SharedOnlyMockBukkit { + + private CustomAnvil plugin; + + @Test + public void startup() { + boolean beforeStart = PluginManagerFiredEventClassMatcher + .hasNotFiredEventInstance(CAConfigReadyEvent.class) + .matches(server.getPluginManager()); + + Assertions.assertTrue(beforeStart, "Somehow, event fired before plugin being loaded ?"); + + // Load the plugin + plugin = MockBukkit.load(CustomAnvil.class); + + boolean postStart = PluginManagerFiredEventClassMatcher + .hasNotFiredEventInstance(CAConfigReadyEvent.class) + .matches(server.getPluginManager()); + + Assertions.assertTrue(postStart, "Event fired before plugin finished being loaded"); + + // Config load phase + server.getScheduler().performOneTick(); + boolean postConfig = PluginManagerFiredEventClassMatcher + .hasFiredEventInstance(CAConfigReadyEvent.class) + .matches(server.getPluginManager()); + + Assertions.assertTrue(postConfig, "Event did not fire after the config phase"); + } + + @AfterEach + public void pluginTeardown() { + if (plugin != null) { + server.getPluginManager().disablePlugin(plugin); + plugin = null; + } + } + +} diff --git a/src/test/java/xyz/alexcrea/cuanvil/api/event/CAEnchantRegistryReadyEventTest.java b/src/test/java/xyz/alexcrea/cuanvil/api/event/CAEnchantRegistryReadyEventTest.java new file mode 100644 index 0000000..2c79388 --- /dev/null +++ b/src/test/java/xyz/alexcrea/cuanvil/api/event/CAEnchantRegistryReadyEventTest.java @@ -0,0 +1,49 @@ +package xyz.alexcrea.cuanvil.api.event; + +import io.delilaheve.CustomAnvil; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockbukkit.mockbukkit.MockBukkit; +import org.mockbukkit.mockbukkit.matcher.plugin.PluginManagerFiredEventClassMatcher; +import xyz.alexcrea.cuanvil.tests.SharedOnlyMockBukkit; + +public class CAEnchantRegistryReadyEventTest extends SharedOnlyMockBukkit { + + private CustomAnvil plugin; + + @Test + public void startup() { + boolean beforeStart = PluginManagerFiredEventClassMatcher + .hasNotFiredEventInstance(CAEnchantRegistryReadyEvent.class) + .matches(server.getPluginManager()); + + Assertions.assertTrue(beforeStart, "Somehow, event fired before plugin being loaded ?"); + + // Load the plugin + plugin = MockBukkit.load(CustomAnvil.class); + + boolean postStart = PluginManagerFiredEventClassMatcher + .hasNotFiredEventInstance(CAEnchantRegistryReadyEvent.class) + .matches(server.getPluginManager()); + + Assertions.assertTrue(postStart, "Event fired before plugin finished being loaded"); + + // Config load phase + server.getScheduler().performOneTick(); + boolean postConfig = PluginManagerFiredEventClassMatcher + .hasFiredEventInstance(CAEnchantRegistryReadyEvent.class) + .matches(server.getPluginManager()); + + Assertions.assertTrue(postConfig, "Event did not fire after the config phase"); + } + + @AfterEach + public void pluginTeardown() { + if (plugin != null) { + server.getPluginManager().disablePlugin(plugin); + plugin = null; + } + } + +} diff --git a/src/test/java/xyz/alexcrea/cuanvil/data/AnvilClickTestData.java b/src/test/java/xyz/alexcrea/cuanvil/data/AnvilClickTestData.java index 65d7ddb..002b194 100644 --- a/src/test/java/xyz/alexcrea/cuanvil/data/AnvilClickTestData.java +++ b/src/test/java/xyz/alexcrea/cuanvil/data/AnvilClickTestData.java @@ -1,8 +1,11 @@ package xyz.alexcrea.cuanvil.data; +import org.bukkit.entity.Player; import org.bukkit.event.Event; +import org.bukkit.inventory.AnvilInventory; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.Nullable; +import xyz.alexcrea.cuanvil.util.AnvilFuseTestUtil; public record AnvilClickTestData( @Nullable ItemStack leftItem, @@ -47,4 +50,8 @@ public record AnvilClickTestData( int levelCost) { this(expectedCursor, levelCost, null); } + + public void executeTest(AnvilInventory anvil, Player player){ + AnvilFuseTestUtil.executeAnvilClickTest(anvil, player, this); + } } diff --git a/src/test/java/xyz/alexcrea/cuanvil/data/AnvilFuseTestData.java b/src/test/java/xyz/alexcrea/cuanvil/data/AnvilFuseTestData.java index a01d0ab..8542d41 100644 --- a/src/test/java/xyz/alexcrea/cuanvil/data/AnvilFuseTestData.java +++ b/src/test/java/xyz/alexcrea/cuanvil/data/AnvilFuseTestData.java @@ -1,7 +1,10 @@ package xyz.alexcrea.cuanvil.data; +import org.bukkit.entity.HumanEntity; +import org.bukkit.inventory.AnvilInventory; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.Nullable; +import xyz.alexcrea.cuanvil.util.AnvilFuseTestUtil; public record AnvilFuseTestData( @Nullable ItemStack leftItem, @@ -51,4 +54,8 @@ public record AnvilFuseTestData( ); } + public void executeTest(AnvilInventory anvil, HumanEntity player){ + AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, this); + } + } diff --git a/src/test/java/xyz/alexcrea/cuanvil/data/TestDataContainer.java b/src/test/java/xyz/alexcrea/cuanvil/data/TestDataContainer.java index 2c8ff93..b4f47a5 100644 --- a/src/test/java/xyz/alexcrea/cuanvil/data/TestDataContainer.java +++ b/src/test/java/xyz/alexcrea/cuanvil/data/TestDataContainer.java @@ -16,17 +16,17 @@ public record TestDataContainer( ) { public void executeTest(AnvilInventory anvil, Player player) { - executeFuseTest(anvil, player); - if (clickData != null) executeClickTest(anvil, player); + fuseData.executeTest(anvil, player); + if (clickData != null) clickData.executeTest(anvil, player); } public void executeFuseTest(AnvilInventory anvil, HumanEntity player) { - AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, fuseData); + fuseData.executeTest(anvil, player); } public void executeClickTest(AnvilInventory anvil, Player player) { Assertions.assertNotNull(clickData); - AnvilFuseTestUtil.executeAnvilClickTest(anvil, player, clickData); + clickData.executeTest(anvil, player); } public @NotNull TestDataContainer nullifyResult() { diff --git a/src/test/java/xyz/alexcrea/cuanvil/mock/AnvilViewMock.java b/src/test/java/xyz/alexcrea/cuanvil/mock/AnvilViewMock.java index cdb4c44..afcbb26 100644 --- a/src/test/java/xyz/alexcrea/cuanvil/mock/AnvilViewMock.java +++ b/src/test/java/xyz/alexcrea/cuanvil/mock/AnvilViewMock.java @@ -60,12 +60,12 @@ public class AnvilViewMock extends PlayerInventoryViewMock implements AnvilView @Override public boolean bypassesEnchantmentLevelRestriction() { - throw new UnsupportedOperationException("Custom anvil was not think with this existing"); + throw new UnsupportedOperationException("This is currently is not used in CustomAnvil"); } @Override public void bypassEnchantmentLevelRestriction(boolean bypassEnchantmentLevelRestriction) { - throw new UnsupportedOperationException("Custom anvil was not think with this existing"); + throw new UnsupportedOperationException("This is currently is not used in CustomAnvil"); } @Override @@ -73,4 +73,9 @@ public class AnvilViewMock extends PlayerInventoryViewMock implements AnvilView return top; } + @Override + public void open() { + throw new UnsupportedOperationException("This is currently is not used in CustomAnvil"); + } + } diff --git a/src/test/java/xyz/alexcrea/cuanvil/tests/DefaultCustomAnvilTest.java b/src/test/java/xyz/alexcrea/cuanvil/tests/DefaultCustomAnvilTest.java index 3a1c399..bdb280c 100644 --- a/src/test/java/xyz/alexcrea/cuanvil/tests/DefaultCustomAnvilTest.java +++ b/src/test/java/xyz/alexcrea/cuanvil/tests/DefaultCustomAnvilTest.java @@ -1,7 +1,9 @@ package xyz.alexcrea.cuanvil.tests; import io.delilaheve.CustomAnvil; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.mockbukkit.mockbukkit.MockBukkit; import org.mockbukkit.mockbukkit.ServerMock; @@ -13,13 +15,16 @@ import java.util.List; public abstract class DefaultCustomAnvilTest { - protected ServerMock server; + protected static ServerMock server; protected CustomAnvil plugin; + @BeforeAll + public static void setupMock() { + server = MockBukkit.mock(); + } + @BeforeEach public void setUp() { - // Start the mock server - server = MockBukkit.mock(); // Load your plugin plugin = MockBukkit.load(CustomAnvil.class); // Continue initialization of the plugin @@ -28,9 +33,6 @@ public abstract class DefaultCustomAnvilTest { @AfterEach public void tearDown() { - // Stop the mock server - MockBukkit.unmock(); - // Unregister enchantments List toUnregister = new ArrayList<>( CAEnchantmentRegistry.getInstance().values() @@ -40,6 +42,13 @@ public abstract class DefaultCustomAnvilTest { CAEnchantmentRegistry.getInstance().unregister(caEnchantment); } + server.getPluginManager().disablePlugin(plugin); + } + + @AfterAll + public static void tearDownMock() { + // Stop the mock server + MockBukkit.unmock(); } } diff --git a/src/test/java/xyz/alexcrea/cuanvil/util/AnvilFuseTestUtil.java b/src/test/java/xyz/alexcrea/cuanvil/util/AnvilFuseTestUtil.java index b73b884..6f5c7bb 100644 --- a/src/test/java/xyz/alexcrea/cuanvil/util/AnvilFuseTestUtil.java +++ b/src/test/java/xyz/alexcrea/cuanvil/util/AnvilFuseTestUtil.java @@ -110,22 +110,60 @@ public class AnvilFuseTestUtil { Assertions.assertEquals(player.getOpenInventory().getTopInventory(), anvil, "Openned inventory is not anvil"); + ItemStack afterLeft = data.expectedAfterLeftPlaced(); + ItemStack afterRight = data.expectedAfterRightPlaced(); + ItemStack afterBoth = data.expectedResult(); + // Fist, test null result(s) + // Test with only the left item - anvil.setItem(1, null); // We clear the right slot in case something was there - testPlacingItem(anvil, player, - 0, data.expectedPriceAfterLeftPlaced(), - data.leftItem(), data.expectedAfterLeftPlaced()); + if(afterLeft == null){ + anvil.setItem(1, null); // We clear the right slot in case something was there + testPlacingItem(anvil, player, + 0, data.expectedPriceAfterLeftPlaced(), + data.leftItem(), null); + } // Test with only the right item - anvil.setItem(0, null); // We only want the right item. so we remove the left one - testPlacingItem(anvil, player, - 1, data.expectedPriceAfterRightPlaced(), - data.rightItem(), data.expectedAfterRightPlaced()); + if(afterRight == null){ + anvil.setItem(0, null); // We only want the right item. so we remove the left one + testPlacingItem(anvil, player, + 1, data.expectedPriceAfterRightPlaced(), + data.rightItem(), null); + } // Test with both placed - testPlacingItem(anvil, player, - 0, data.expectedPriceAfterBothPlaced(), - data.leftItem(), data.expectedResult()); + if(afterBoth == null){ + anvil.setItem(0, data.leftItem()); + testPlacingItem(anvil, player, + 1, data.expectedPriceAfterBothPlaced(), + data.rightItem(), data.expectedResult()); + } + + // Then, test non null result(s) + + // Test with only the left item + if(afterLeft != null){ + anvil.setItem(1, null); // We clear the right slot in case something was there + testPlacingItem(anvil, player, + 0, data.expectedPriceAfterLeftPlaced(), + data.leftItem(), afterLeft); + } + + // Test with only the right item + if(afterRight != null){ + anvil.setItem(0, null); // We only want the right item. so we remove the left one + testPlacingItem(anvil, player, + 1, data.expectedPriceAfterRightPlaced(), + data.rightItem(), afterRight); + } + + // Test with both placed + if(afterBoth != null){ + anvil.setItem(0, data.leftItem()); + testPlacingItem(anvil, player, + 1, data.expectedPriceAfterBothPlaced(), + data.rightItem(), afterBoth); + } } public static void executeAnvilClickTest( @@ -139,6 +177,7 @@ public class AnvilFuseTestUtil { ItemStack result = anvil.getResult(); player.setLevel(0); + player.setExp(0); player.setItemOnCursor(null); // Do a test with not enough level @@ -151,11 +190,12 @@ public class AnvilFuseTestUtil { assertEqual(null, player.getItemOnCursor()); } player.setLevel(data.levelCost()); + player.setExp(0); player.setItemOnCursor(null); simulateClick(anvil, player, data.expectedResult()); - // Should have similated the click + // Should have simulated the click assertEqual(data.leftItem(), anvil.getFirstItem()); assertEqual(data.rightItem(), anvil.getSecondItem()); assertEqual(data.resultSlotItem(), anvil.getResult()); @@ -208,7 +248,7 @@ public class AnvilFuseTestUtil { public static void assertEqual(@Nullable ItemStack expected, @Nullable ItemStack other) { boolean secondIsAir = isAir(other); if (isAir(expected)) - Assertions.assertTrue(secondIsAir, "Item " + other + " was not air but was expected to be"); + Assertions.assertTrue(secondIsAir, "Item " + other + " was not air but was expected to be."); else { Assertions.assertFalse(secondIsAir, "Item " + other + " is air but was expected to be " + expected); @@ -220,12 +260,12 @@ public class AnvilFuseTestUtil { } public static boolean isAir(@Nullable ItemStack item) { - return item == null || item.isEmpty(); + return item == null || item.isEmpty() || item.getAmount() == 0; } public static void assertPriceEqual(Integer expectedPrice, int price) { if (expectedPrice == null) return; - Assertions.assertEquals(expectedPrice, price); + Assertions.assertEquals(expectedPrice, price, "Price of anvil fuse was wrong"); } } diff --git a/src/test/resources/plugin.yml b/src/test/resources/plugin.yml index c116cce..37fb95a 100644 --- a/src/test/resources/plugin.yml +++ b/src/test/resources/plugin.yml @@ -11,6 +11,8 @@ libraries: - org.jetbrains.kotlin:kotlin-stdlib:2.0.21 commands: + customanvil: + description: Generic command for custom anvil anvilconfigreload: description: Reload every config of this plugin permission: ca.command.reload @@ -37,6 +39,9 @@ permissions: ca.command.reload: default: op description: Allow administrator to reload the plugin's configs + ca.command.diagnostic: + default: op + description: Get debug information about the plugin and server ca.config.edit: default: op description: Allow administrator to edit the plugin's config in game @@ -55,8 +60,7 @@ permissions: default: op description: Allow player to edit lore via paper if enabled (toggleable) - -# soft depend on old name (UnsafeEnchantsPlus), so I can disable it if it is on the same server (old name for this plugin) +# soft depend on old name of this plugin (UnsafeEnchantsPlus), so I can disable it if it is on the same server # Also depend to other plugin for compatibility softdepend: - UnsafeEnchantsPlus @@ -66,3 +70,4 @@ softdepend: - EcoEnchants - eco - ExcellentEnchants + - HavenBags