diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..7db4ee0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,28 @@ +--- +name: Bug report +about: If you think you encountered something unexpected +title: '' +labels: bug +assignees: alexcrea + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Technical informations** +Server version & framework. (/version) +Plugin version if applicable (/version CustomAnvil) +List of plugin that may interact with CustomAnvil if applicable + +**Errors** +If any errors was displayed on the console. please provide it. + +**Configuration** +Provide relavent configuration files if no error found or if you think the error is related to your configuration. + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..1f9c4e1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,18 @@ +--- +name: Feature request +about: Suggest an idea for Custom Anvil +title: '' +labels: enhancement +assignees: alexcrea + +--- + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Idea how to integrate it** +How and where do you think the config of your feature should like +How and where do you think the config gui for your feature should look like + +**Additional context** +Add any other context about the feature request here. diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml new file mode 100644 index 0000000..a50cacd --- /dev/null +++ b/.github/workflows/gradle.yml @@ -0,0 +1,163 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle + +name: Java CI with Gradle + +on: + push: + branches: [ "v1.x.x", "v2.x.x" ] + pull_request: + 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: write + + 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"]' + + 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 80d6030..982299c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,21 @@ /.gradle/ /build/ /out/ +.lastDeploymentsId + +#nms & impl submodule build directory ignored +/nms/build +/nms/.gradle +/nms/*/build +/nms/*/.gradle +/impl/build +/impl/.gradle +/impl/*/build +/impl/*/.gradle + +# run folder +/run/ + +# other random folders +/htmlReport +/.kotlin/errors diff --git a/.idea/icon.png b/.idea/icon.png new file mode 100644 index 0000000..06945f2 Binary files /dev/null and b/.idea/icon.png differ 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/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md index a842439..9d9e678 100644 --- a/README.md +++ b/README.md @@ -1,59 +1,111 @@ # Custom Anvil -**Custom Anvil** is a plugin that allows server administrators to customise every aspect of the anvil's mechanics. -It is expected to work on 1.18 to 1.21 minecraft servers running spigot or paper. -(the plugin support of 1.16.5 to 1.17.1 is experimental an 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. +**Custom Anvil** is a plugin that allows server administrators to customize every aspect of the anvil's mechanics. ### Download Locations: -the plugin can be downloaded on the -[Spigot site](https://www.spigotmc.org/resources/custom-anvil.114884) -or [on GitHub](https://github.com/alexcrea/CustomAnvil/releases/latest) +the plugin can be downloaded on + [Modrinth](https://modrinth.com/plugin/customanvil), + [Hangar](https://hangar.papermc.io/alexcrea/CustomAnvil) + or here [on GitHub](https://github.com/alexcrea/CustomAnvil/releases/latest) --- **Custom Anvil** have the following features: - 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 "to expensive" when above lv 40. (need ProtocoLib) +- 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 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) + +# 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) ``` -/!\ version under 1.3.1 use other permission. from 1.2.0 to 1.3.1-A1 use ua.unsafe instead of ca.affected -under 1.2.0 replace ca prefix by ue and use ue.unsafe. some permission/features may not exist before the last version. ### Commands -```yml -anvilconfigreload or carl: Reload every config of this plugin -customanvilconfig: open a menu for administrator to edit plugin's config in game -``` ---- -### 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 use the [1.21 configurations](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21) + +run `/customanvil help` to get information about available commands \ +this only show subcommands you have permission for + +### Supported Plugins +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. +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) --- -### Known issue: -There is non known issue, if you find one please report the issue. + +### Default Plugin's Configurations +see [Here](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs) + +--- +### 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: -- Semi manual config update on plugin or minecraft update -- Check unknown registered enchantment & warn -- Warn admin on unsupported minecraft version +- Better Folia support (make gui work. fix some dirty handled parts) +- 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 b4e5baa..a5dc7c3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,57 +1,455 @@ +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 "1.6.21" + kotlin("jvm") version "2.3.0" java + id("org.jetbrains.dokka").version("1.9.20") + id("com.gradleup.shadow").version("9.3.0") + // Maven publish + `maven-publish` + signing + id("cn.lalaki.central").version("1.2.8") + // Paper + 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.5.0-beta" +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 { - mavenCentral() - maven(url = "https://hub.spigotmc.org/nexus/content/repositories/snapshots/") + // EcoEnchants + maven(url = "https://repo.auxilor.io/repository/maven-public/") - // ProtocoLib - maven (url = "https://repo.dmulloy2.net/repository/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 { - - compileOnly(kotlin("stdlib")) - + // Spigot api compileOnly("org.spigotmc:spigot-api:1.18-R0.1-SNAPSHOT") - // Gui library - compileOnly("com.github.stefvanschie.inventoryframework:IF:0.10.14") + // fast stats + implementation("dev.faststats.metrics:bukkit:0.27.0") - // Protocolib - compileOnly("com.comphenix.protocol:ProtocolLib:5.1.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) + testRuntimeOnly(inventoryFramework) // EnchantsSquaredRewritten compileOnly(files("libs/EnchantsSquared.jar")) - testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.0") + // EcoEnchants & item + compileOnly("com.willfp:libreforge:4.79.0:all") + compileOnly("com.willfp:eco:6.74.5") - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") + compileOnly("com.willfp:EcoEnchants:12.11.1") + compileOnly(project(":impl:LegacyEcoEnchant")) -} + compileOnly("com.willfp:EcoItems:5.66.0") -tasks.getByName("test") { - useJUnitPlatform() -} - -// Fat-jar builder -val fatJar = tasks.register("fatJar") { - manifest { - attributes.apply { put("Main-Class", "io.delilaheve.CustomAnvil") } + // ExcellentEnchants + implementation(project(":impl:ExcellentEnchant5_4")) + compileOnly("su.nightexpress.excellentenchants:Core:5.1.0") { + exclude("org.spigotmc") } - archiveFileName.set("${rootProject.name}-${archiveVersion}.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) + 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")) + + // 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: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.48.0") + testRuntimeOnly("commons-lang:commons-lang:2.6") } -// Ensure fatJar and copyJar are run -tasks.getByName("build") { - dependsOn(fatJar) -} \ No newline at end of file +allprojects { + apply(plugin = "java") + apply(plugin = "kotlin") + + repositories { + mavenCentral() + + // Spigot repository + maven(url = "https://hub.spigotmc.org/nexus/content/repositories/snapshots/") + + // Paper repository + maven("https://repo.papermc.io/repository/maven-public/") + } + + dependencies { + compileOnly(kotlin("stdlib")) + + // Test dependency + 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") { + useJUnitPlatform() + } + + // Configure used version of kotlin and java + java { + disableAutoTargetJvm() + toolchain.languageVersion.set(JavaLanguageVersion.of(21)) + } + + // Set target version + tasks.withType().configureEach { + 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" + } + + kotlin { + compilerOptions { + apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2) + jvmTarget.set(JvmTarget.JVM_16) + } + } + +} + + +tasks { + + 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.cuanvil.inventoryframework") + relocate("dev.faststats", "xyz.alexcrea.cuanvil.faststats") + + filesMatching("plugin.yml") { + expand( + "version" to effectiveVersion + processedSuffix, + "libraries" to libraries.joinToString(transform = { "\"$it\"" }), + ) + } + + // Process resource for plugin.yml + dependsOn(processResources) + } + + // 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", + )) + + // Exclude kotlin std, annotations and adventure api + exclude("*kotlin/**") + exclude("**/annotations/**") + exclude("net/kyori/**") + } + + val offlineJar by registering(ShadowJar::class) { + configureBaseShadow("offline", emptyArray()) + + from(sourceSets.main.get().output) + configurations = listOf(project.configurations.runtimeClasspath.get()) + } + + // Make the online and offline jar on build + named("build") { + dependsOn(shadowJar, offlineJar) + } + +} + +val sourcesJar by tasks.registering(Jar::class) { + archiveClassifier.set("sources") + from(kotlin.sourceSets.main.get().kotlin) +} + +val javadocJar by tasks.registering(Jar::class, fun Jar.() { + group = JavaBasePlugin.DOCUMENTATION_GROUP + description = "Assembles Javadoc JAR" + archiveClassifier.set("javadoc") + from(tasks.named("dokkaHtml")) +}) + +signing { + useGpgCmd() + val extension = extensions + .getByName("publishing") as PublishingExtension + sign(extension.publications) +} + +// ------------------------------------ +// PUBLISHING TO SONATYPE CONFIGURATION +// ------------------------------------ + +// The path is recommended to be set to an empty directory +val localMavenRepo = uri( + project.findProperty("localMavenRepo") as String? + ?: rootProject.layout.buildDirectory.dir("local-maven-repo").get().asFile.toURI() // Convert to URI +) + +centralPortalPlus { + url = localMavenRepo + username = System.getenv("SONATYPE_USERNAME") + password = System.getenv("SONATYPE_PASSWORD") + publishingType = BaseCentralPortalPlusExtension.PublishingType.USER_MANAGED // or PublishingType.AUTOMATIC +} + +object Meta { + const val desc = "spigot plugin to control every aspect of the anvil." + const val license = "GPL-3.0" + const val githubRepo = "alexcrea/CustomAnvil" + const val release = "https://s01.oss.sonatype.org/service/local/" + 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 { + url = localMavenRepo // Specify the same local repo path in the configuration. + } + } + + publications { + create("maven") { + groupId = project.group.toString() + artifactId = project.name + version = project.version.toString() + 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) + url.set("https://github.com/${Meta.githubRepo}") + licenses { + license { + name.set(Meta.license) + url.set("https://www.gnu.org/licenses/gpl-3.0.en.html") + } + } + developers { + developer { + id.set("alexcrea") + name.set("alexcrea") + email.set("alexcrea.of@laposte.net") + url.set("https://github.com/alexcrea") + } + } + scm { + url.set( + "https://github.com/${Meta.githubRepo}.git" + ) + connection.set( + "scm:git:git://github.com/${Meta.githubRepo}.git" + ) + developerConnection.set( + "scm:git:git://github.com/${Meta.githubRepo}.git" + ) + } + 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 aa09cf4..b5ad3b0 100644 --- a/defaultconfigs/1.18/config.yml +++ b/defaultconfigs/1.18/config.yml @@ -1,3 +1,21 @@ +# +# 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: @@ -50,10 +68,54 @@ unit_repair_cost: 1 # Valid values include 0 to 1000 sacrifice_illegal_enchant_cost: 1 -# Default limit to apply to any enchants missing from override_limits +# Allow using color code and hexadecimal color. # -# Valid values include 1 to 1000 -default_limit: 5 +# 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. +# +# 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 # @@ -61,48 +123,49 @@ 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: - aqua_affinity: 1 - binding_curse: 1 - channeling: 1 - flame: 1 - infinity: 1 - mending: 1 - multishot: 1 - silk_touch: 1 - vanishing_curse: 1 - depth_strider: 3 # anything more than 3 is treated as 3 by the game - protection: 4 - fire_protection: 4 - blast_protection: 4 - projectile_protection: 4 - feather_falling: 4 - thorns: 3 - respiration: 3 - sharpness: 5 - smite: 5 - bane_of_arthropods: 5 - knockback: 2 - fire_aspect: 2 - looting: 3 - sweeping: 3 - sweeping_edge: 3 - efficiency: 5 - unbreaking: 3 - fortune: 3 - power: 5 - punch: 2 - luck_of_the_sea: 3 - lure: 3 - frost_walker: 2 - impaling: 5 - riptide: 3 - loyalty: 3 - piercing: 4 - quick_charge: 3 - soul_speed: 3 - swift_sneak: 3 + 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 # Multipliers used to calculate the enchantment's value in repair/combining # @@ -116,131 +179,288 @@ enchant_limits: # 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: - aqua_affinity: + minecraft:aqua_affinity: item: 4 book: 2 - bane_of_arthropods: + minecraft:bane_of_arthropods: item: 2 book: 1 - binding_curse: + minecraft:binding_curse: item: 8 book: 4 - blast_protection: + minecraft:blast_protection: item: 4 book: 2 - channeling: + minecraft:channeling: item: 8 book: 4 - depth_strider: + minecraft:depth_strider: item: 4 book: 2 - efficiency: + minecraft:efficiency: item: 1 book: 1 - flame: + minecraft:flame: item: 4 book: 2 - feather_falling: + minecraft:feather_falling: item: 2 book: 1 - fire_aspect: + minecraft:fire_aspect: item: 4 book: 2 - fire_protection: + minecraft:fire_protection: item: 2 book: 1 - fortune: + minecraft:fortune: item: 4 book: 2 - frost_walker: + minecraft:frost_walker: item: 4 book: 2 - impaling: + minecraft:impaling: item: 4 book: 2 - infinity: + minecraft:infinity: item: 8 book: 4 - knockback: + minecraft:knockback: item: 2 book: 1 - looting: + minecraft:looting: item: 4 book: 2 - loyalty: + minecraft:loyalty: item: 1 book: 1 - luck_of_the_sea: + minecraft:luck_of_the_sea: item: 4 book: 2 - lure: + minecraft:lure: item: 4 book: 2 - mending: + minecraft:mending: item: 4 book: 2 - multishot: + minecraft:multishot: item: 4 book: 2 - piercing: + minecraft:piercing: item: 1 book: 1 - power: + minecraft:power: item: 1 book: 1 - projectile_protection: + minecraft:projectile_protection: item: 2 book: 1 - protection: + minecraft:protection: item: 1 book: 1 - punch: + minecraft:punch: item: 4 book: 2 - quick_charge: + minecraft:quick_charge: item: 2 book: 1 - respiration: + minecraft:respiration: item: 4 book: 2 - riptide: + minecraft:riptide: item: 4 book: 2 - silk_touch: + minecraft:silk_touch: item: 8 book: 4 - sharpness: + minecraft:sharpness: item: 1 book: 1 - smite: + minecraft:smite: item: 2 book: 1 - soul_speed: + minecraft:soul_speed: item: 8 book: 4 - swift_sneak: + minecraft:swift_sneak: item: 8 book: 4 - sweeping: + minecraft:sweeping: item: 4 book: 2 - sweeping_edge: + minecraft:sweeping_edge: item: 4 book: 2 - thorns: + minecraft:thorns: item: 8 book: 4 - unbreaking: + minecraft:unbreaking: item: 2 book: 1 - vanishing_curse: + minecraft:vanishing_curse: item: 8 book: 4 +# 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.4.5 +configVersion: 1.11.0 diff --git a/defaultconfigs/1.18/enchant_conflict.yml b/defaultconfigs/1.18/enchant_conflict.yml index 387fc90..45d62c3 100644 --- a/defaultconfigs/1.18/enchant_conflict.yml +++ b/defaultconfigs/1.18/enchant_conflict.yml @@ -1,3 +1,8 @@ +# +# 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: @@ -12,158 +17,162 @@ # ---------------------------------------------------- restriction_aqua_affinity: - enchantments: [ aqua_affinity ] + enchantments: [ minecraft:aqua_affinity ] notAffectedGroups: [ enchanted_book, helmets ] restriction_bane_of_arthropods: - enchantments: [ bane_of_arthropods ] + enchantments: [ minecraft:bane_of_arthropods ] notAffectedGroups: [ enchanted_book, melee_weapons ] restriction_blast_protection: - enchantments: [ blast_protection ] + enchantments: [ minecraft:blast_protection ] notAffectedGroups: [ enchanted_book, armors ] restriction_channeling: - enchantments: [ channeling ] + enchantments: [ minecraft:channeling ] notAffectedGroups: [ enchanted_book, trident ] restriction_binding_curse: - enchantments: [ binding_curse ] + enchantments: [ minecraft:binding_curse ] notAffectedGroups: [ enchanted_book, wearable ] restriction_vanishing_curse: - enchantments: [ vanishing_curse ] + enchantments: [ minecraft:vanishing_curse ] notAffectedGroups: [ enchanted_book, can_vanish ] restriction_depth_strider: - enchantments: [ depth_strider ] + enchantments: [ minecraft:depth_strider ] notAffectedGroups: [ enchanted_book, boots ] restriction_efficiency: - enchantments: [ efficiency ] + enchantments: [ minecraft:efficiency ] notAffectedGroups: [ enchanted_book, tools, shears ] restriction_feather_falling: - enchantments: [ feather_falling ] + enchantments: [ minecraft:feather_falling ] notAffectedGroups: [ enchanted_book, boots ] restriction_fire_aspect: - enchantments: [ fire_aspect ] + enchantments: [ minecraft:fire_aspect ] notAffectedGroups: [ enchanted_book, swords ] restriction_fire_protection: - enchantments: [ fire_protection ] + enchantments: [ minecraft:fire_protection ] notAffectedGroups: [ enchanted_book, armors ] restriction_flame: - enchantments: [ flame ] + enchantments: [ minecraft:flame ] notAffectedGroups: [ enchanted_book, bow ] restriction_fortune: - enchantments: [ fortune ] + enchantments: [ minecraft:fortune ] notAffectedGroups: [ enchanted_book, tools ] restriction_frost_walker: - enchantments: [ frost_walker ] + enchantments: [ minecraft:frost_walker ] notAffectedGroups: [ enchanted_book, boots ] restriction_impaling: - enchantments: [ impaling ] + enchantments: [ minecraft:impaling ] notAffectedGroups: [ enchanted_book, trident ] restriction_infinity: - enchantments: [ infinity ] + enchantments: [ minecraft:infinity ] notAffectedGroups: [ enchanted_book, bow ] restriction_knockback: - enchantments: [ knockback ] + enchantments: [ minecraft:knockback ] notAffectedGroups: [ enchanted_book, swords ] restriction_looting: - enchantments: [ looting ] + enchantments: [ minecraft:looting ] notAffectedGroups: [ enchanted_book, swords ] restriction_loyalty: - enchantments: [ 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: [ lure ] + enchantments: [ minecraft:lure ] notAffectedGroups: [ enchanted_book, fishing_rod ] restriction_mending: - enchantments: [ mending ] + enchantments: [ minecraft:mending ] notAffectedGroups: [ enchanted_book, can_unbreak ] -restriction_multishot: - enchantments: [ multishot ] +restriction_minecraft_multishot: + enchantments: [ minecraft:multishot ] notAffectedGroups: [ enchanted_book, crossbow ] restriction_piercing: - enchantments: [ piercing ] + enchantments: [ minecraft:piercing ] notAffectedGroups: [ enchanted_book, crossbow ] restriction_power: - enchantments: [ power ] + enchantments: [ minecraft:power ] notAffectedGroups: [ enchanted_book, bow ] restriction_projectile_protection: - enchantments: [ projectile_protection ] + enchantments: [ minecraft:projectile_protection ] notAffectedGroups: [ enchanted_book, armors ] restriction_protection: - enchantments: [ protection ] + enchantments: [ minecraft:protection ] notAffectedGroups: [ enchanted_book, armors ] restriction_punch: - enchantments: [ punch ] + enchantments: [ minecraft:punch ] notAffectedGroups: [ enchanted_book, bow ] restriction_quick_charge: - enchantments: [ quick_charge ] + enchantments: [ minecraft:quick_charge ] notAffectedGroups: [ enchanted_book, crossbow ] restriction_respiration: - enchantments: [ respiration ] + enchantments: [ minecraft:respiration ] notAffectedGroups: [ enchanted_book, helmets ] restriction_riptide: - enchantments: [ riptide ] + enchantments: [ minecraft:riptide ] notAffectedGroups: [ enchanted_book, trident ] restriction_sharpness: - enchantments: [ sharpness ] + enchantments: [ minecraft:sharpness ] notAffectedGroups: [ enchanted_book, melee_weapons ] -restriction_silk_touch: - enchantments: [ silk_touch ] +restriction__silk_touch: + enchantments: [ minecraft:silk_touch ] notAffectedGroups: [ enchanted_book, tools ] restriction_smite: - enchantments: [ smite ] + enchantments: [ minecraft:smite ] notAffectedGroups: [ enchanted_book, melee_weapons ] restriction_soul_speed: - enchantments: [ soul_speed ] + enchantments: [ minecraft:soul_speed ] notAffectedGroups: [ enchanted_book, boots ] restriction_sweeping_edge: - enchantments: [ sweeping, 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: [ swift_sneak ] + enchantments: [ minecraft:swift_sneak ] notAffectedGroups: [ enchanted_book, leggings ] restriction_thorns: - enchantments: [ thorns ] + enchantments: [ minecraft:thorns ] notAffectedGroups: [ enchanted_book, armors ] -restriction_unbreaking: - enchantments: [ unbreaking ] +restriction__unbreaking: + enchantments: [ minecraft:unbreaking ] notAffectedGroups: [ enchanted_book, can_unbreak ] # ---------------------------------------------------- @@ -175,60 +184,60 @@ restriction_unbreaking: sword_enchant_conflict: enchantments: - - bane_of_arthropods - - smite - - sharpness + - minecraft:bane_of_arthropods + - minecraft:smite + - minecraft:sharpness notAffectedGroups: [ ] maxEnchantmentBeforeConflict: 1 protection_enchant_conflict: enchantments: - - blast_protection - - fire_protection - - projectile_protection - - protection + - minecraft:blast_protection + - minecraft:fire_protection + - minecraft:projectile_protection + - minecraft:protection notAffectedGroups: [ ] maxEnchantmentBeforeConflict: 1 trident_conflict1: enchantments: - - channeling - - riptide + - minecraft:channeling + - minecraft:riptide notAffectedGroups: [ ] maxEnchantmentBeforeConflict: 1 trident_conflict2: enchantments: - - loyalty - - riptide + - minecraft:loyalty + - minecraft:riptide notAffectedGroups: [ ] maxEnchantmentBeforeConflict: 1 boot_conflict: enchantments: - - depth_strider - - frost_walker + - minecraft:depth_strider + - minecraft:frost_walker notAffectedGroups: [ ] maxEnchantmentBeforeConflict: 1 tool_conflict: enchantments: - - fortune - - silk_touch + - minecraft:fortune + - minecraft:silk_touch notAffectedGroups: [ ] maxEnchantmentBeforeConflict: 1 bow_conflict: enchantments: - - mending - - infinity + - minecraft:mending + - minecraft:infinity notAffectedGroups: [ ] maxEnchantmentBeforeConflict: 1 crossbow_conflict: enchantments: - - multishot - - piercing + - minecraft:multishot + - minecraft:piercing notAffectedGroups: [ ] maxEnchantmentBeforeConflict: 1 diff --git a/defaultconfigs/1.18/item_groups.yml b/defaultconfigs/1.18/item_groups.yml index 2e222aa..3c1eb5d 100644 --- a/defaultconfigs/1.18/item_groups.yml +++ b/defaultconfigs/1.18/item_groups.yml @@ -1,3 +1,8 @@ +# +# 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: diff --git a/defaultconfigs/1.18/unit_repair_item.yml b/defaultconfigs/1.18/unit_repair_item.yml index 0e96878..2902cce 100644 --- a/defaultconfigs/1.18/unit_repair_item.yml +++ b/defaultconfigs/1.18/unit_repair_item.yml @@ -1,3 +1,8 @@ +# +# 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 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 aaf35f1..5d59e5a 100644 --- a/defaultconfigs/1.21/config.yml +++ b/defaultconfigs/1.21/config.yml @@ -1,3 +1,21 @@ +# +# 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: @@ -50,10 +68,54 @@ unit_repair_cost: 1 # Valid values include 0 to 1000 sacrifice_illegal_enchant_cost: 1 -# Default limit to apply to any enchants missing from override_limits +# Allow using color code and hexadecimal color. # -# Valid values include 1 to 1000 -default_limit: 5 +# 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 # @@ -61,51 +123,49 @@ 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: - aqua_affinity: 1 - binding_curse: 1 - channeling: 1 - flame: 1 - infinity: 1 - mending: 1 - multishot: 1 - silk_touch: 1 - vanishing_curse: 1 - depth_strider: 3 # anything more than 3 is treated as 3 by the game - protection: 4 - fire_protection: 4 - blast_protection: 4 - projectile_protection: 4 - feather_falling: 4 - thorns: 3 - respiration: 3 - sharpness: 5 - smite: 5 - bane_of_arthropods: 5 - knockback: 2 - fire_aspect: 2 - looting: 3 - sweeping: 3 - sweeping_edge: 3 - efficiency: 5 - unbreaking: 3 - fortune: 3 - power: 5 - punch: 2 - luck_of_the_sea: 3 - lure: 3 - frost_walker: 2 - impaling: 5 - riptide: 3 - loyalty: 3 - piercing: 4 - quick_charge: 3 - soul_speed: 3 - swift_sneak: 3 - density: 5 - breach: 4 - wind_burst: 3 + 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 # Multipliers used to calculate the enchantment's value in repair/combining # @@ -119,135 +179,283 @@ enchant_limits: # 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: - aqua_affinity: + minecraft:aqua_affinity: item: 4 book: 2 - bane_of_arthropods: + minecraft:bane_of_arthropods: item: 2 book: 1 - binding_curse: + minecraft:binding_curse: item: 8 book: 4 - blast_protection: + minecraft:blast_protection: item: 4 book: 2 - channeling: + minecraft:channeling: item: 8 book: 4 - depth_strider: + minecraft:depth_strider: item: 4 book: 2 - efficiency: + minecraft:efficiency: item: 1 book: 1 - flame: + minecraft:flame: item: 4 book: 2 - feather_falling: + minecraft:feather_falling: item: 2 book: 1 - fire_aspect: + minecraft:fire_aspect: item: 4 book: 2 - fire_protection: + minecraft:fire_protection: item: 2 book: 1 - fortune: + minecraft:fortune: item: 4 book: 2 - frost_walker: + minecraft:frost_walker: item: 4 book: 2 - impaling: + minecraft:impaling: item: 4 book: 2 - infinity: + minecraft:infinity: item: 8 book: 4 - knockback: + minecraft:knockback: item: 2 book: 1 - looting: + minecraft:looting: item: 4 book: 2 - loyalty: + minecraft:loyalty: item: 1 book: 1 - luck_of_the_sea: + minecraft:luck_of_the_sea: item: 4 book: 2 - lure: + minecraft:lure: item: 4 book: 2 - mending: + minecraft:mending: item: 4 book: 2 - multishot: + minecraft:multishot: item: 4 book: 2 - piercing: + minecraft:piercing: item: 1 book: 1 - power: + minecraft:power: item: 1 book: 1 - projectile_protection: + minecraft:projectile_protection: item: 2 book: 1 - protection: + minecraft:protection: item: 1 book: 1 - punch: + minecraft:punch: item: 4 book: 2 - quick_charge: + minecraft:quick_charge: item: 2 book: 1 - respiration: + minecraft:respiration: item: 4 book: 2 - riptide: + minecraft:riptide: item: 4 book: 2 - silk_touch: + minecraft:silk_touch: item: 8 book: 4 - sharpness: + minecraft:sharpness: item: 1 book: 1 - smite: + minecraft:smite: item: 2 book: 1 - soul_speed: + minecraft:soul_speed: item: 8 book: 4 - swift_sneak: + minecraft:swift_sneak: item: 8 book: 4 - sweeping: + minecraft:sweeping: item: 4 book: 2 - sweeping_edge: + minecraft:sweeping_edge: item: 4 book: 2 - thorns: + minecraft:thorns: item: 8 book: 4 - unbreaking: + minecraft:unbreaking: item: 2 book: 1 - vanishing_curse: + minecraft:vanishing_curse: item: 8 book: 4 - density: - item: 1 - book: 1 - breach: - item: 4 - book: 2 - 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 @@ -255,5 +463,4 @@ debug_log: false # Whether to show verbose debug logging debug_log_verbose: false -configVersion: 1.4.5 -lowMinecraftVersion: 1.21 +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 77e7fef..45d62c3 100644 --- a/defaultconfigs/1.21/enchant_conflict.yml +++ b/defaultconfigs/1.21/enchant_conflict.yml @@ -1,3 +1,8 @@ +# +# 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: @@ -12,278 +17,163 @@ # ---------------------------------------------------- restriction_aqua_affinity: - enchantments: - - aqua_affinity - notAffectedGroups: - - enchanted_book - - helmets + enchantments: [ minecraft:aqua_affinity ] + notAffectedGroups: [ enchanted_book, helmets ] restriction_bane_of_arthropods: - enchantments: - - bane_of_arthropods - notAffectedGroups: - - enchanted_book - - melee_weapons - - mace + enchantments: [ minecraft:bane_of_arthropods ] + notAffectedGroups: [ enchanted_book, melee_weapons ] restriction_blast_protection: - enchantments: - - blast_protection - notAffectedGroups: - - enchanted_book - - armors + enchantments: [ minecraft:blast_protection ] + notAffectedGroups: [ enchanted_book, armors ] restriction_channeling: - enchantments: - - channeling - notAffectedGroups: - - enchanted_book - - trident + enchantments: [ minecraft:channeling ] + notAffectedGroups: [ enchanted_book, trident ] restriction_binding_curse: - enchantments: - - binding_curse - notAffectedGroups: - - enchanted_book - - wearable + enchantments: [ minecraft:binding_curse ] + notAffectedGroups: [ enchanted_book, wearable ] restriction_vanishing_curse: - enchantments: - - vanishing_curse - notAffectedGroups: - - enchanted_book - - can_vanish + enchantments: [ minecraft:vanishing_curse ] + notAffectedGroups: [ enchanted_book, can_vanish ] restriction_depth_strider: - enchantments: - - depth_strider - notAffectedGroups: - - enchanted_book - - boots + enchantments: [ minecraft:depth_strider ] + notAffectedGroups: [ enchanted_book, boots ] restriction_efficiency: - enchantments: - - efficiency - notAffectedGroups: - - enchanted_book - - tools - - shears + enchantments: [ minecraft:efficiency ] + notAffectedGroups: [ enchanted_book, tools, shears ] restriction_feather_falling: - enchantments: - - feather_falling - notAffectedGroups: - - enchanted_book - - boots + enchantments: [ minecraft:feather_falling ] + notAffectedGroups: [ enchanted_book, boots ] restriction_fire_aspect: - enchantments: - - fire_aspect - notAffectedGroups: - - enchanted_book - - swords - - mace + enchantments: [ minecraft:fire_aspect ] + notAffectedGroups: [ enchanted_book, swords ] restriction_fire_protection: - enchantments: - - fire_protection - notAffectedGroups: - - enchanted_book - - armors + enchantments: [ minecraft:fire_protection ] + notAffectedGroups: [ enchanted_book, armors ] restriction_flame: - enchantments: - - flame - notAffectedGroups: - - enchanted_book - - bow + enchantments: [ minecraft:flame ] + notAffectedGroups: [ enchanted_book, bow ] restriction_fortune: - enchantments: - - fortune - notAffectedGroups: - - enchanted_book - - tools + enchantments: [ minecraft:fortune ] + notAffectedGroups: [ enchanted_book, tools ] restriction_frost_walker: - enchantments: - - frost_walker - notAffectedGroups: - - enchanted_book - - boots + enchantments: [ minecraft:frost_walker ] + notAffectedGroups: [ enchanted_book, boots ] restriction_impaling: - enchantments: - - impaling - notAffectedGroups: - - enchanted_book - - trident + enchantments: [ minecraft:impaling ] + notAffectedGroups: [ enchanted_book, trident ] restriction_infinity: - enchantments: - - infinity - notAffectedGroups: - - enchanted_book - - bow + enchantments: [ minecraft:infinity ] + notAffectedGroups: [ enchanted_book, bow ] restriction_knockback: - enchantments: - - knockback - notAffectedGroups: - - enchanted_book - - swords + enchantments: [ minecraft:knockback ] + notAffectedGroups: [ enchanted_book, swords ] restriction_looting: - enchantments: - - looting - notAffectedGroups: - - enchanted_book - - swords + enchantments: [ minecraft:looting ] + notAffectedGroups: [ enchanted_book, swords ] restriction_loyalty: - enchantments: - - loyalty - notAffectedGroups: - - enchanted_book - - trident + 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: - - lure - notAffectedGroups: - - enchanted_book - - fishing_rod + enchantments: [ minecraft:lure ] + notAffectedGroups: [ enchanted_book, fishing_rod ] restriction_mending: - enchantments: - - mending - notAffectedGroups: - - enchanted_book - - can_unbreak + enchantments: [ minecraft:mending ] + notAffectedGroups: [ enchanted_book, can_unbreak ] -restriction_multishot: - enchantments: - - multishot - notAffectedGroups: - - enchanted_book - - crossbow +restriction_minecraft_multishot: + enchantments: [ minecraft:multishot ] + notAffectedGroups: [ enchanted_book, crossbow ] restriction_piercing: - enchantments: - - piercing - notAffectedGroups: - - enchanted_book - - crossbow + enchantments: [ minecraft:piercing ] + notAffectedGroups: [ enchanted_book, crossbow ] restriction_power: - enchantments: - - power - notAffectedGroups: - - enchanted_book - - bow + enchantments: [ minecraft:power ] + notAffectedGroups: [ enchanted_book, bow ] restriction_projectile_protection: - enchantments: - - projectile_protection - notAffectedGroups: - - enchanted_book - - armors + enchantments: [ minecraft:projectile_protection ] + notAffectedGroups: [ enchanted_book, armors ] restriction_protection: - enchantments: - - protection - notAffectedGroups: - - enchanted_book - - armors + enchantments: [ minecraft:protection ] + notAffectedGroups: [ enchanted_book, armors ] restriction_punch: - enchantments: - - punch - notAffectedGroups: - - enchanted_book - - bow + enchantments: [ minecraft:punch ] + notAffectedGroups: [ enchanted_book, bow ] restriction_quick_charge: - enchantments: - - quick_charge - notAffectedGroups: - - enchanted_book - - crossbow + enchantments: [ minecraft:quick_charge ] + notAffectedGroups: [ enchanted_book, crossbow ] restriction_respiration: - enchantments: - - respiration - notAffectedGroups: - - enchanted_book - - helmets + enchantments: [ minecraft:respiration ] + notAffectedGroups: [ enchanted_book, helmets ] restriction_riptide: - enchantments: - - riptide - notAffectedGroups: - - enchanted_book - - trident + enchantments: [ minecraft:riptide ] + notAffectedGroups: [ enchanted_book, trident ] restriction_sharpness: - enchantments: - - sharpness - notAffectedGroups: - - enchanted_book - - melee_weapons + enchantments: [ minecraft:sharpness ] + notAffectedGroups: [ enchanted_book, melee_weapons ] -restriction_silk_touch: - enchantments: - - silk_touch - notAffectedGroups: - - enchanted_book - - tools +restriction__silk_touch: + enchantments: [ minecraft:silk_touch ] + notAffectedGroups: [ enchanted_book, tools ] restriction_smite: - enchantments: - - smite - notAffectedGroups: - - enchanted_book - - melee_weapons - - mace + enchantments: [ minecraft:smite ] + notAffectedGroups: [ enchanted_book, melee_weapons ] restriction_soul_speed: - enchantments: - - soul_speed - notAffectedGroups: - - enchanted_book - - boots + enchantments: [ minecraft:soul_speed ] + notAffectedGroups: [ enchanted_book, boots ] restriction_sweeping_edge: - enchantments: - - sweeping - - sweeping_edge - notAffectedGroups: - - enchanted_book - - swords + 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: - - swift_sneak - notAffectedGroups: - - enchanted_book - - leggings + enchantments: [ minecraft:swift_sneak ] + notAffectedGroups: [ enchanted_book, leggings ] restriction_thorns: - enchantments: - - thorns - notAffectedGroups: - - enchanted_book - - armors + enchantments: [ minecraft:thorns ] + notAffectedGroups: [ enchanted_book, armors ] -restriction_unbreaking: - enchantments: - - unbreaking - notAffectedGroups: - - enchanted_book - - can_unbreak +restriction__unbreaking: + enchantments: [ minecraft:unbreaking ] + notAffectedGroups: [ enchanted_book, can_unbreak ] # ---------------------------------------------------- # Now we have conflicts about enchantment Incompatibility @@ -294,83 +184,61 @@ restriction_unbreaking: sword_enchant_conflict: enchantments: - - bane_of_arthropods - - smite - - sharpness - notAffectedGroups: [] + - minecraft:bane_of_arthropods + - minecraft:smite + - minecraft:sharpness + notAffectedGroups: [ ] maxEnchantmentBeforeConflict: 1 protection_enchant_conflict: enchantments: - - blast_protection - - fire_protection - - projectile_protection - - protection - notAffectedGroups: [] + - minecraft:blast_protection + - minecraft:fire_protection + - minecraft:projectile_protection + - minecraft:protection + notAffectedGroups: [ ] maxEnchantmentBeforeConflict: 1 trident_conflict1: enchantments: - - channeling - - riptide - notAffectedGroups: [] + - minecraft:channeling + - minecraft:riptide + notAffectedGroups: [ ] maxEnchantmentBeforeConflict: 1 trident_conflict2: enchantments: - - loyalty - - riptide - notAffectedGroups: [] + - minecraft:loyalty + - minecraft:riptide + notAffectedGroups: [ ] maxEnchantmentBeforeConflict: 1 boot_conflict: enchantments: - - depth_strider - - frost_walker - notAffectedGroups: [] + - minecraft:depth_strider + - minecraft:frost_walker + notAffectedGroups: [ ] maxEnchantmentBeforeConflict: 1 tool_conflict: enchantments: - - fortune - - silk_touch - notAffectedGroups: [] + - minecraft:fortune + - minecraft:silk_touch + notAffectedGroups: [ ] maxEnchantmentBeforeConflict: 1 bow_conflict: enchantments: - - mending - - infinity - notAffectedGroups: [] + - minecraft:mending + - minecraft:infinity + notAffectedGroups: [ ] maxEnchantmentBeforeConflict: 1 crossbow_conflict: enchantments: - - multishot - - piercing - notAffectedGroups: [] - maxEnchantmentBeforeConflict: 1 -restriction_density: - enchantments: - - density - notAffectedGroups: - - mace -restriction_breach: - enchantments: - - breach - notAffectedGroups: - - mace -restriction_wind_burst: - enchantments: - - wind_burst - notAffectedGroups: - - mace -mace_enchant_conflict: - enchantments: - - density - - breach - - smite - - bane_of_arthropods + - minecraft:multishot + - minecraft:piercing + notAffectedGroups: [ ] maxEnchantmentBeforeConflict: 1 # ---------------------------------------------------- diff --git a/defaultconfigs/1.21/item_groups.yml b/defaultconfigs/1.21/item_groups.yml index 2703bec..3c1eb5d 100644 --- a/defaultconfigs/1.21/item_groups.yml +++ b/defaultconfigs/1.21/item_groups.yml @@ -1,3 +1,8 @@ +# +# 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: @@ -15,16 +20,16 @@ nothing: example_include: type: include items: - - stone - - polished_granite + - stone + - polished_granite # This group contain everything except polished granite and elements of example_include example_exclude: type: exclude items: - - polished_granite + - polished_granite groups: - - example_include + - example_include # Default configuration should be vanilla enchantment conflict group # there may have error, if you find one you can fix it ! @@ -33,176 +38,173 @@ example_exclude: swords: type: include items: - - wooden_sword - - stone_sword - - iron_sword - - diamond_sword - - golden_sword - - netherite_sword + - wooden_sword + - stone_sword + - iron_sword + - diamond_sword + - golden_sword + - netherite_sword axes: type: include items: - - wooden_axe - - stone_axe - - iron_axe - - diamond_axe - - golden_axe - - netherite_axe + - wooden_axe + - stone_axe + - iron_axe + - diamond_axe + - golden_axe + - netherite_axe melee_weapons: type: include groups: - - swords - - axes + - swords + - axes helmets: type: include items: - - leather_helmet - - chainmail_helmet - - iron_helmet - - diamond_helmet - - golden_helmet - - netherite_helmet - - turtle_helmet + - leather_helmet + - chainmail_helmet + - iron_helmet + - diamond_helmet + - golden_helmet + - netherite_helmet + - turtle_helmet chestplate: type: include items: - - leather_chestplate - - chainmail_chestplate - - iron_chestplate - - diamond_chestplate - - golden_chestplate - - netherite_chestplate + - leather_chestplate + - chainmail_chestplate + - iron_chestplate + - diamond_chestplate + - golden_chestplate + - netherite_chestplate leggings: type: include items: - - leather_leggings - - chainmail_leggings - - iron_leggings - - diamond_leggings - - golden_leggings - - netherite_leggings + - leather_leggings + - chainmail_leggings + - iron_leggings + - diamond_leggings + - golden_leggings + - netherite_leggings boots: type: include items: - - leather_boots - - chainmail_boots - - iron_boots - - diamond_boots - - golden_boots - - netherite_boots + - leather_boots + - chainmail_boots + - iron_boots + - diamond_boots + - golden_boots + - netherite_boots armors: type: include groups: - - helmets - - chestplate - - leggings - - boots + - 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 + - elytra + - carved_pumpkin + - skeleton_skull + - wither_skeleton_skull + - zombie_head + - player_head + - creeper_head + - dragon_head + # do not exist in 1.18 but exist in future update + - piglin_head groups: - - armors + - armors tools: type: include items: - - wooden_pickaxe - - stone_pickaxe - - iron_pickaxe - - diamond_pickaxe - - golden_pickaxe - - netherite_pickaxe - - wooden_shovel - - stone_shovel - - iron_shovel - - diamond_shovel - - golden_shovel - - netherite_shovel - - wooden_hoe - - stone_hoe - - iron_hoe - - diamond_hoe - - golden_hoe - - netherite_hoe + - wooden_pickaxe + - stone_pickaxe + - iron_pickaxe + - diamond_pickaxe + - golden_pickaxe + - netherite_pickaxe + - wooden_shovel + - stone_shovel + - iron_shovel + - diamond_shovel + - golden_shovel + - netherite_shovel + - wooden_hoe + - stone_hoe + - iron_hoe + - diamond_hoe + - golden_hoe + - netherite_hoe groups: - - axes + - axes enchanted_book: type: include items: - - enchanted_book + - enchanted_book trident: type: include items: - - trident + - trident bow: type: include items: - - bow + - bow crossbow: type: include items: - - crossbow + - crossbow fishing_rod: type: include items: - - fishing_rod + - fishing_rod shears: type: include items: - - shears + - shears can_unbreak: type: include items: - - elytra - - flint_and_steel - - shield - - carrot_on_a_stick - - warped_fungus_on_a_stick - - brush + - elytra + - flint_and_steel + - shield + - carrot_on_a_stick + - warped_fungus_on_a_stick + # do not exist in 1.18 but exist in future update + - brush groups: - - melee_weapons - - tools - - armors - - trident - - bow - - crossbow - - fishing_rod - - shears - - mace + - melee_weapons + - tools + - armors + - trident + - bow + - crossbow + - fishing_rod + - shears can_vanish: type: include items: - - compass + - compass groups: - - wearable - - can_unbreak -mace: - type: include - items: - - mace + - wearable + - can_unbreak diff --git a/defaultconfigs/1.21/unit_repair_item.yml b/defaultconfigs/1.21/unit_repair_item.yml index 0e96878..2902cce 100644 --- a/defaultconfigs/1.21/unit_repair_item.yml +++ b/defaultconfigs/1.21/unit_repair_item.yml @@ -1,3 +1,8 @@ +# +# 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 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 29e08e8..4397d2e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1,13 @@ -kotlin.code.style=official \ No newline at end of file +kotlin.code.style=official + +# Signing +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 + diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 69a9715..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-7.1-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/.gitignore b/impl/LegacyEcoEnchant/.gitignore new file mode 100644 index 0000000..47374f1 --- /dev/null +++ b/impl/LegacyEcoEnchant/.gitignore @@ -0,0 +1 @@ +.lastDeploymentsId \ No newline at end of file diff --git a/impl/LegacyEcoEnchant/build.gradle.kts b/impl/LegacyEcoEnchant/build.gradle.kts new file mode 100644 index 0000000..83057a7 --- /dev/null +++ b/impl/LegacyEcoEnchant/build.gradle.kts @@ -0,0 +1,13 @@ +group = rootProject.group +version = rootProject.version + +plugins { + kotlin("jvm") version "2.3.0" +} + +// Imitate needed class and method to support legacy version of EcoEnchant +dependencies { + // Spigot api + compileOnly("org.spigotmc:spigot-api:1.18-R0.1-SNAPSHOT") + +} \ No newline at end of file diff --git a/impl/LegacyEcoEnchant/src/main/java/com/willfp/ecoenchants/enchantments/EcoEnchant.java b/impl/LegacyEcoEnchant/src/main/java/com/willfp/ecoenchants/enchantments/EcoEnchant.java new file mode 100644 index 0000000..edfe1e9 --- /dev/null +++ b/impl/LegacyEcoEnchant/src/main/java/com/willfp/ecoenchants/enchantments/EcoEnchant.java @@ -0,0 +1,26 @@ +package com.willfp.ecoenchants.enchantments; + +import com.willfp.ecoenchants.enchantments.meta.EnchantmentTarget; +import com.willfp.ecoenchants.enchantments.meta.EnchantmentType; +import org.bukkit.enchantments.Enchantment; +import org.jetbrains.annotations.NotNull; + +import java.util.Set; + +/** + * Mock class for legacy package of eco enchants + */ +public class EcoEnchant { + + public boolean conflictsWith(@NotNull Enchantment enchant) { + return false; + } + + public Set getTargets() { + return null; + } + + public EnchantmentType getType() { + return null; + } +} diff --git a/impl/LegacyEcoEnchant/src/main/java/com/willfp/ecoenchants/enchantments/EcoEnchants.java b/impl/LegacyEcoEnchant/src/main/java/com/willfp/ecoenchants/enchantments/EcoEnchants.java new file mode 100644 index 0000000..39f8064 --- /dev/null +++ b/impl/LegacyEcoEnchant/src/main/java/com/willfp/ecoenchants/enchantments/EcoEnchants.java @@ -0,0 +1,14 @@ +package com.willfp.ecoenchants.enchantments; + +import java.util.List; + +/** + * Mock class for legacy package of eco enchants + */ +public class EcoEnchants { + + public static List values(){ + return null; // We don't care here. + } + +} diff --git a/impl/LegacyEcoEnchant/src/main/java/com/willfp/ecoenchants/enchantments/meta/EnchantmentTarget.java b/impl/LegacyEcoEnchant/src/main/java/com/willfp/ecoenchants/enchantments/meta/EnchantmentTarget.java new file mode 100644 index 0000000..19a667b --- /dev/null +++ b/impl/LegacyEcoEnchant/src/main/java/com/willfp/ecoenchants/enchantments/meta/EnchantmentTarget.java @@ -0,0 +1,16 @@ +package com.willfp.ecoenchants.enchantments.meta; + +import org.bukkit.Material; + +import java.util.Set; + +/** + * Mock class for legacy package of eco enchants + */ +public class EnchantmentTarget { + + public Set getMaterials() { + return null; + } + +} diff --git a/impl/LegacyEcoEnchant/src/main/java/com/willfp/ecoenchants/enchantments/meta/EnchantmentType.java b/impl/LegacyEcoEnchant/src/main/java/com/willfp/ecoenchants/enchantments/meta/EnchantmentType.java new file mode 100644 index 0000000..183ec74 --- /dev/null +++ b/impl/LegacyEcoEnchant/src/main/java/com/willfp/ecoenchants/enchantments/meta/EnchantmentType.java @@ -0,0 +1,9 @@ +package com.willfp.ecoenchants.enchantments.meta; + +public class EnchantmentType { + + public boolean isSingular() { + return false; + } + +} 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/Disenchantment-6.1.5.jar b/libs/Disenchantment-6.1.5.jar new file mode 100644 index 0000000..a574f38 Binary files /dev/null and b/libs/Disenchantment-6.1.5.jar differ diff --git a/libs/EnchantsSquared.jar b/libs/EnchantsSquared.jar index e8df965..add5768 100644 Binary files a/libs/EnchantsSquared.jar and b/libs/EnchantsSquared.jar differ diff --git a/libs/ExcellentEnchants-4.1.0-striped.jar b/libs/ExcellentEnchants-4.1.0-striped.jar new file mode 100644 index 0000000..a3500aa Binary files /dev/null and b/libs/ExcellentEnchants-4.1.0-striped.jar 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/HavenBags-1.31.0.1760.jar b/libs/HavenBags-1.31.0.1760.jar new file mode 100644 index 0000000..aed122d Binary files /dev/null and b/libs/HavenBags-1.31.0.1760.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/nightcore-2.7.3.jar b/libs/nightcore-2.7.3.jar new file mode 100644 index 0000000..7ecad8e Binary files /dev/null and b/libs/nightcore-2.7.3.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/.gitignore b/nms/nms-common/.gitignore new file mode 100644 index 0000000..4f41170 --- /dev/null +++ b/nms/nms-common/.gitignore @@ -0,0 +1 @@ +.lastDeploymentsId diff --git a/nms/nms-common/build.gradle.kts b/nms/nms-common/build.gradle.kts new file mode 100644 index 0000000..950d807 --- /dev/null +++ b/nms/nms-common/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 { + // Used for nms + paperweight.paperDevBundle("1.21.10-R0.1-SNAPSHOT") + + // Protocolib + 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/packet/NoPacketManager.kt b/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/NoPacketManager.kt new file mode 100644 index 0000000..c3367f5 --- /dev/null +++ b/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/NoPacketManager.kt @@ -0,0 +1,14 @@ +package xyz.alexcrea.cuanvil.dependency.packet + +import org.bukkit.entity.Player + +class NoPacketManager: PacketManager { + + override val canSetInstantBuild: Boolean + get() = false + + override fun setInstantBuild(player: Player, instantBuild: Boolean) { + // ProtocoLib not installed and not in a supported version: We do nothing + } + +} diff --git a/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManager.kt b/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManager.kt new file mode 100644 index 0000000..857bfab --- /dev/null +++ b/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManager.kt @@ -0,0 +1,17 @@ +package xyz.alexcrea.cuanvil.dependency.packet + +import org.bukkit.entity.Player + +interface PacketManager { + + /** + * If the provided packet manager if able to set instant build. + */ + val canSetInstantBuild: Boolean + + /** + * Try to set instant build properties + */ + fun setInstantBuild(player: Player, instantBuild: Boolean) + +} diff --git a/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerBase.kt b/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerBase.kt new file mode 100644 index 0000000..184aa0e --- /dev/null +++ b/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerBase.kt @@ -0,0 +1,16 @@ +package xyz.alexcrea.cuanvil.dependency.packet + +import org.bukkit.entity.Player +import org.bukkit.event.Listener + +abstract class PacketManagerBase() : PacketManager, Listener { + + override val canSetInstantBuild: Boolean + get() = false + + override fun setInstantBuild(player: Player, instantBuild: Boolean) { + // Default implementation is empty. + } + + +} diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/protocolib/ProtocoLibWrapper.kt b/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/ProtocoLibWrapper.kt similarity index 91% rename from src/main/kotlin/xyz/alexcrea/cuanvil/dependency/protocolib/ProtocoLibWrapper.kt rename to nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/ProtocoLibWrapper.kt index 8a52e31..8143b6b 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/protocolib/ProtocoLibWrapper.kt +++ b/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/ProtocoLibWrapper.kt @@ -1,4 +1,4 @@ -package xyz.alexcrea.cuanvil.dependency.protocolib +package xyz.alexcrea.cuanvil.dependency.packet import com.comphenix.protocol.PacketType import com.comphenix.protocol.ProtocolLibrary @@ -11,7 +11,7 @@ class ProtocoLibWrapper: PacketManager { private val protocolManager: ProtocolManager = ProtocolLibrary.getProtocolManager(); - override val isProtocoLibInstalled: Boolean + override val canSetInstantBuild: Boolean get() = true override fun setInstantBuild(player: Player, instantBuild: Boolean) { diff --git a/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/scheduler/TaskScheduler.kt b/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/scheduler/TaskScheduler.kt new file mode 100644 index 0000000..e6a70dd --- /dev/null +++ b/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/scheduler/TaskScheduler.kt @@ -0,0 +1,18 @@ +package xyz.alexcrea.cuanvil.dependency.scheduler + +import org.bukkit.entity.Entity +import org.bukkit.plugin.Plugin + +interface TaskScheduler { + + fun scheduleGlobally(plugin: Plugin, task: Runnable, time: Long): Any? + fun scheduleGlobally(plugin: Plugin, task: Runnable): Any?{ + return scheduleGlobally(plugin, task, 0L) + } + + fun scheduleOnEntity(plugin: Plugin, entity: Entity, task: Runnable, time: Long): Any? + fun scheduleOnEntity(plugin: Plugin, entity: Entity, task: Runnable): Any?{ + return scheduleOnEntity(plugin, entity, task, 0L) + } + +} \ 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/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/scheduler/FoliaScheduler.kt b/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/scheduler/FoliaScheduler.kt new file mode 100644 index 0000000..a9bd79f --- /dev/null +++ b/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/scheduler/FoliaScheduler.kt @@ -0,0 +1,40 @@ +package xyz.alexcrea.cuanvil.dependency.scheduler + +import io.papermc.paper.threadedregions.scheduler.ScheduledTask +import org.bukkit.Bukkit +import org.bukkit.entity.Entity +import org.bukkit.plugin.Plugin +import java.util.function.Consumer + +class FoliaScheduler : TaskScheduler { + override fun scheduleGlobally(plugin: Plugin, task: Runnable, time: Long): Any? { + if(time < 1){ + return Bukkit.getGlobalRegionScheduler().run( + plugin + ) { scheduledTask: ScheduledTask? -> task.run() } + } + return Bukkit.getGlobalRegionScheduler().runDelayed( + plugin, + { scheduledTask: ScheduledTask? -> task.run() }, + time + ) + } + + + override fun scheduleOnEntity(plugin: Plugin, entity: Entity, task: Runnable, time: Long): Any? { + if(time < 1){ + return entity.scheduler.run( + plugin, + { scheduledTask: ScheduledTask? -> task.run() }, + {} + ) + } + return entity.scheduler.runDelayed( + plugin, + { scheduledTask: ScheduledTask? -> task.run() }, + {}, + time + ) + } + +} 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/.gitignore b/nms/v1_17R1/.gitignore new file mode 100644 index 0000000..47374f1 --- /dev/null +++ b/nms/v1_17R1/.gitignore @@ -0,0 +1 @@ +.lastDeploymentsId \ No newline at end of file diff --git a/nms/v1_17R1/build.gradle.kts b/nms/v1_17R1/build.gradle.kts new file mode 100644 index 0000000..c354b1c --- /dev/null +++ b/nms/v1_17R1/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.17.1-R0.1-SNAPSHOT") +} + +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/v1_17R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_17R1_PacketManager.kt b/nms/v1_17R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_17R1_PacketManager.kt new file mode 100644 index 0000000..c820eab --- /dev/null +++ b/nms/v1_17R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_17R1_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.v1_17_R1.entity.CraftPlayer +import org.bukkit.entity.Player +import xyz.alexcrea.cuanvil.dependency.packet.PacketManager +import xyz.alexcrea.cuanvil.dependency.packet.PacketManagerBase + +class V1_17R1_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_18R1/.gitignore b/nms/v1_18R1/.gitignore new file mode 100644 index 0000000..47374f1 --- /dev/null +++ b/nms/v1_18R1/.gitignore @@ -0,0 +1 @@ +.lastDeploymentsId \ No newline at end of file diff --git a/nms/v1_18R1/build.gradle.kts b/nms/v1_18R1/build.gradle.kts new file mode 100644 index 0000000..44873d5 --- /dev/null +++ b/nms/v1_18R1/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.18.1-R0.1-SNAPSHOT") +} + +repositories { + maven("https://repo.papermc.io/repository/maven-public/") + +} + +// Set target version +tasks.withType().configureEach { + sourceCompatibility = "17" + targetCompatibility = "17" + + options.encoding = "UTF-8" +} + +kotlin { + compilerOptions { + 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/packet/versions/V1_18R1_PacketManager.kt b/nms/v1_18R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_18R1_PacketManager.kt new file mode 100644 index 0000000..71df5c7 --- /dev/null +++ b/nms/v1_18R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_18R1_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.v1_18_R1.entity.CraftPlayer +import org.bukkit.entity.Player +import xyz.alexcrea.cuanvil.dependency.packet.PacketManager +import xyz.alexcrea.cuanvil.dependency.packet.PacketManagerBase + +class V1_18R1_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_18R2/.gitignore b/nms/v1_18R2/.gitignore new file mode 100644 index 0000000..47374f1 --- /dev/null +++ b/nms/v1_18R2/.gitignore @@ -0,0 +1 @@ +.lastDeploymentsId \ No newline at end of file diff --git a/nms/v1_18R2/build.gradle.kts b/nms/v1_18R2/build.gradle.kts new file mode 100644 index 0000000..cf349ec --- /dev/null +++ b/nms/v1_18R2/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.18.2-R0.1-SNAPSHOT") +} + +repositories { + maven("https://repo.papermc.io/repository/maven-public/") + +} + +// Set target version +tasks.withType().configureEach { + sourceCompatibility = "17" + targetCompatibility = "17" + + options.encoding = "UTF-8" +} + +kotlin { + compilerOptions { + 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/packet/versions/V1_18R2_PacketManager.kt b/nms/v1_18R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_18R2_PacketManager.kt new file mode 100644 index 0000000..ee442f5 --- /dev/null +++ b/nms/v1_18R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_18R2_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.v1_18_R2.entity.CraftPlayer +import org.bukkit.entity.Player +import xyz.alexcrea.cuanvil.dependency.packet.PacketManager +import xyz.alexcrea.cuanvil.dependency.packet.PacketManagerBase + +class V1_18R2_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_19R1/.gitignore b/nms/v1_19R1/.gitignore new file mode 100644 index 0000000..47374f1 --- /dev/null +++ b/nms/v1_19R1/.gitignore @@ -0,0 +1 @@ +.lastDeploymentsId \ No newline at end of file diff --git a/nms/v1_19R1/build.gradle.kts b/nms/v1_19R1/build.gradle.kts new file mode 100644 index 0000000..9791b9b --- /dev/null +++ b/nms/v1_19R1/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.19.2-R0.1-SNAPSHOT") +} + +repositories { + maven("https://repo.papermc.io/repository/maven-public/") + +} + +// Set target version +tasks.withType().configureEach { + sourceCompatibility = "17" + targetCompatibility = "17" + + options.encoding = "UTF-8" +} + +kotlin { + compilerOptions { + 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/packet/versions/V1_19R1_PacketManager.kt b/nms/v1_19R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_19R1_PacketManager.kt new file mode 100644 index 0000000..7a057fc --- /dev/null +++ b/nms/v1_19R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_19R1_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.v1_19_R1.entity.CraftPlayer +import org.bukkit.entity.Player +import xyz.alexcrea.cuanvil.dependency.packet.PacketManager +import xyz.alexcrea.cuanvil.dependency.packet.PacketManagerBase + +class V1_19R1_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_19R2/.gitignore b/nms/v1_19R2/.gitignore new file mode 100644 index 0000000..47374f1 --- /dev/null +++ b/nms/v1_19R2/.gitignore @@ -0,0 +1 @@ +.lastDeploymentsId \ No newline at end of file diff --git a/nms/v1_19R2/build.gradle.kts b/nms/v1_19R2/build.gradle.kts new file mode 100644 index 0000000..88f1a8a --- /dev/null +++ b/nms/v1_19R2/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.19.3-R0.1-SNAPSHOT") +} + +repositories { + maven("https://repo.papermc.io/repository/maven-public/") + +} + +// Set target version +tasks.withType().configureEach { + sourceCompatibility = "17" + targetCompatibility = "17" + + options.encoding = "UTF-8" +} + +kotlin { + compilerOptions { + 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/packet/versions/V1_19R2_PacketManager.kt b/nms/v1_19R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_19R2_PacketManager.kt new file mode 100644 index 0000000..0d04cd1 --- /dev/null +++ b/nms/v1_19R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_19R2_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.v1_19_R2.entity.CraftPlayer +import org.bukkit.entity.Player +import xyz.alexcrea.cuanvil.dependency.packet.PacketManager +import xyz.alexcrea.cuanvil.dependency.packet.PacketManagerBase + +class V1_19R2_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_19R3/.gitignore b/nms/v1_19R3/.gitignore new file mode 100644 index 0000000..47374f1 --- /dev/null +++ b/nms/v1_19R3/.gitignore @@ -0,0 +1 @@ +.lastDeploymentsId \ No newline at end of file diff --git a/nms/v1_19R3/build.gradle.kts b/nms/v1_19R3/build.gradle.kts new file mode 100644 index 0000000..3d609af --- /dev/null +++ b/nms/v1_19R3/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.19.4-R0.1-SNAPSHOT") +} + +repositories { + maven("https://repo.papermc.io/repository/maven-public/") + +} + +// Set target version +tasks.withType().configureEach { + sourceCompatibility = "17" + targetCompatibility = "17" + + options.encoding = "UTF-8" +} + +kotlin { + compilerOptions { + 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/packet/versions/V1_19R3_PacketManager.kt b/nms/v1_19R3/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_19R3_PacketManager.kt new file mode 100644 index 0000000..7c72791 --- /dev/null +++ b/nms/v1_19R3/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_19R3_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.v1_19_R3.entity.CraftPlayer +import org.bukkit.entity.Player +import xyz.alexcrea.cuanvil.dependency.packet.PacketManager +import xyz.alexcrea.cuanvil.dependency.packet.PacketManagerBase + +class V1_19R3_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_20R1/.gitignore b/nms/v1_20R1/.gitignore new file mode 100644 index 0000000..47374f1 --- /dev/null +++ b/nms/v1_20R1/.gitignore @@ -0,0 +1 @@ +.lastDeploymentsId \ No newline at end of file diff --git a/nms/v1_20R1/build.gradle.kts b/nms/v1_20R1/build.gradle.kts new file mode 100644 index 0000000..67b46b5 --- /dev/null +++ b/nms/v1_20R1/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.20.1-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/v1_20R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_20R1_PacketManager.kt b/nms/v1_20R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_20R1_PacketManager.kt new file mode 100644 index 0000000..1fbac83 --- /dev/null +++ b/nms/v1_20R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_20R1_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.v1_20_R1.entity.CraftPlayer +import org.bukkit.entity.Player +import xyz.alexcrea.cuanvil.dependency.packet.PacketManager +import xyz.alexcrea.cuanvil.dependency.packet.PacketManagerBase + +class V1_20R1_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_20R2/.gitignore b/nms/v1_20R2/.gitignore new file mode 100644 index 0000000..47374f1 --- /dev/null +++ b/nms/v1_20R2/.gitignore @@ -0,0 +1 @@ +.lastDeploymentsId \ No newline at end of file diff --git a/nms/v1_20R2/build.gradle.kts b/nms/v1_20R2/build.gradle.kts new file mode 100644 index 0000000..e8417cd --- /dev/null +++ b/nms/v1_20R2/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.20.2-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/v1_20R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_20R2_PacketManager.kt b/nms/v1_20R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_20R2_PacketManager.kt new file mode 100644 index 0000000..a2db371 --- /dev/null +++ b/nms/v1_20R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_20R2_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.v1_20_R2.entity.CraftPlayer +import org.bukkit.entity.Player +import xyz.alexcrea.cuanvil.dependency.packet.PacketManager +import xyz.alexcrea.cuanvil.dependency.packet.PacketManagerBase + +class V1_20R2_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_20R3/.gitignore b/nms/v1_20R3/.gitignore new file mode 100644 index 0000000..47374f1 --- /dev/null +++ b/nms/v1_20R3/.gitignore @@ -0,0 +1 @@ +.lastDeploymentsId \ No newline at end of file diff --git a/nms/v1_20R3/build.gradle.kts b/nms/v1_20R3/build.gradle.kts new file mode 100644 index 0000000..c49ff48 --- /dev/null +++ b/nms/v1_20R3/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.20.4-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/v1_20R3/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_20R3_PacketManager.kt b/nms/v1_20R3/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_20R3_PacketManager.kt new file mode 100644 index 0000000..51c2ecb --- /dev/null +++ b/nms/v1_20R3/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_20R3_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.v1_20_R3.entity.CraftPlayer +import org.bukkit.entity.Player +import xyz.alexcrea.cuanvil.dependency.packet.PacketManager +import xyz.alexcrea.cuanvil.dependency.packet.PacketManagerBase + +class V1_20R3_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_20R4/.gitignore b/nms/v1_20R4/.gitignore new file mode 100644 index 0000000..47374f1 --- /dev/null +++ b/nms/v1_20R4/.gitignore @@ -0,0 +1 @@ +.lastDeploymentsId \ No newline at end of file diff --git a/nms/v1_20R4/build.gradle.kts b/nms/v1_20R4/build.gradle.kts new file mode 100644 index 0000000..3b98361 --- /dev/null +++ b/nms/v1_20R4/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.20.6-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_0) + jvmTarget.set(JvmTarget.JVM_18) + } +} diff --git a/nms/v1_20R4/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_20R4_PacketManager.kt b/nms/v1_20R4/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_20R4_PacketManager.kt new file mode 100644 index 0000000..ac1e504 --- /dev/null +++ b/nms/v1_20R4/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_20R4_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_20R4_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_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/.gitignore b/nms/v1_21R1/.gitignore new file mode 100644 index 0000000..47374f1 --- /dev/null +++ b/nms/v1_21R1/.gitignore @@ -0,0 +1 @@ +.lastDeploymentsId \ No newline at end of file diff --git a/nms/v1_21R1/build.gradle.kts b/nms/v1_21R1/build.gradle.kts new file mode 100644 index 0000000..bf2152c --- /dev/null +++ b/nms/v1_21R1/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.1-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_21R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_21R1_PacketManager.kt b/nms/v1_21R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_21R1_PacketManager.kt new file mode 100644 index 0000000..4ea9950 --- /dev/null +++ b/nms/v1_21R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_21R1_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_21R1_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_21R2/.gitignore b/nms/v1_21R2/.gitignore new file mode 100644 index 0000000..47374f1 --- /dev/null +++ b/nms/v1_21R2/.gitignore @@ -0,0 +1 @@ +.lastDeploymentsId \ No newline at end of file diff --git a/nms/v1_21R2/build.gradle.kts b/nms/v1_21R2/build.gradle.kts new file mode 100644 index 0000000..e65f6fd --- /dev/null +++ b/nms/v1_21R2/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.3-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_21R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_21R2_PacketManager.kt b/nms/v1_21R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_21R2_PacketManager.kt new file mode 100644 index 0000000..9d88c20 --- /dev/null +++ b/nms/v1_21R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_21R2_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_21R2_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_21R3/.gitignore b/nms/v1_21R3/.gitignore new file mode 100644 index 0000000..47374f1 --- /dev/null +++ b/nms/v1_21R3/.gitignore @@ -0,0 +1 @@ +.lastDeploymentsId \ No newline at end of file diff --git a/nms/v1_21R3/build.gradle.kts b/nms/v1_21R3/build.gradle.kts new file mode 100644 index 0000000..d06816c --- /dev/null +++ b/nms/v1_21R3/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.4-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_21R3/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_21R3_PacketManager.kt b/nms/v1_21R3/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_21R3_PacketManager.kt new file mode 100644 index 0000000..0f581d9 --- /dev/null +++ b/nms/v1_21R3/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_21R3_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_21R3_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_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 03fe07e..9de7d8c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,2 +1,22 @@ rootProject.name = "CustomAnvil" +// NMS subproject +include("nms:nms-common") +findProject(":nms:nms-common")?.name = "nms-common" +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" +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 new file mode 100644 index 0000000..4292fa0 --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/api/AnvilRecipeBuilder.java @@ -0,0 +1,283 @@ +package xyz.alexcrea.cuanvil.api; + +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import xyz.alexcrea.cuanvil.recipe.AnvilCustomRecipe; + +/** + * A Builder for custom craft using anvil. + */ +@SuppressWarnings("unused") +public class AnvilRecipeBuilder { + + private @NotNull String name; + private boolean exactCount; + + private int levelCostPerCraft; + private int linearXpCostPerCraft; + + private boolean removeExactLinearXp; + + private @Nullable ItemStack leftItem; + private @Nullable ItemStack rightItem; + private @Nullable ItemStack resultItem; + + /** + * Instantiates a new Anvil recipe builder. + * exact count default to true. + * xp level and linear cost per craft default to 0. + * + * @param name The recipe name + */ + public AnvilRecipeBuilder(@NotNull String name) { + this.name = name; + + this.exactCount = true; + this.levelCostPerCraft = 0; + this.linearXpCostPerCraft = 0; + this.removeExactLinearXp = false; + + this.leftItem = null; + this.rightItem = null; + this.resultItem = null; + } + + /** + * Gets the recipe name. + * + * @return This recipe builder instance. + */ + @NotNull + public String getName() { + return name; + } + + /** + * Sets the recipe name. + * + * @param name The recipe name + * @return This recipe builder instance. + */ + public AnvilRecipeBuilder setName(String name) { + this.name = name; + return this; + } + + /** + * 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. + * + * @return If the recipe is exact count. + */ + public boolean isExactCount() { + return exactCount; + } + + /** + * Sets if the recipe is exact count. + *

+ * 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. + * + * @param exactCount If the recipe is exact count + * @return This recipe builder instance. + */ + public AnvilRecipeBuilder setExactCount(boolean exactCount) { + this.exactCount = exactCount; + return this; + } + + /** + * 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 getLevelCostPerCraft(); + } + + /** + * Sets the xp level cost per craft. + * + * @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) { + 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; + } + + /** + * Get the left item of the recipe. + * If null (default) then the recipe will not be able to be registered. + * + * @return The left item + */ + @Nullable + public ItemStack getLeftItem() { + return leftItem; + } + + /** + * Set the left item. + * If null (default) then the recipe will not be able to be registered. + * + * @param leftItem the left item + * @return This recipe builder instance. + */ + public AnvilRecipeBuilder setLeftItem(ItemStack leftItem) { + this.leftItem = leftItem; + return this; + } + + /** + * Get the recipe right item. + * null on default new instance. + * + * @return The right item + */ + @Nullable + public ItemStack getRightItem() { + return rightItem; + } + + /** + * Set the recipe right item. + * null on default new instance. + * + * @param rightItem the right item + * @return This recipe builder instance. + */ + public AnvilRecipeBuilder setRightItem(ItemStack rightItem) { + this.rightItem = rightItem; + return this; + } + + /** + * Get the recipe result item. + * If null (default) then the recipe will not be able to be registered. + * + * @return The result item + */ + @Nullable + public ItemStack getResultItem() { + return resultItem; + } + + /** + * Set the recipe result item. + * If null (default) then the recipe will not be able to be registered. + * + * @param resultItem The result item + * @return This recipe builder instance. + */ + public AnvilRecipeBuilder setResultItem(ItemStack resultItem) { + this.resultItem = resultItem; + return this; + } + + /** + * Build the anvil custom recipe. + * Should probably use {@link #registerIfAbsent() registerIfAbsent} or {@link ConflictAPI#addConflict(ConflictBuilder) addConflict}. + * + * @return A new anvil custom recipe base on this builder. + */ + @Nullable // null if missing argument + public AnvilCustomRecipe build() { + if (leftItem == null || resultItem == null) return null; + + return new AnvilCustomRecipe( + this.name, + this.exactCount, + this.levelCostPerCraft, + this.linearXpCostPerCraft, + this.removeExactLinearXp, + this.leftItem, this.rightItem, this.resultItem + ); + } + + /** + * Register this recipe if absent. + * Equivalent to {@link ConflictAPI#addConflict(ConflictBuilder)} + * + * @return True if successful. + */ + public boolean registerIfAbsent() { + return CustomAnvilRecipeApi.addRecipe(this); + } + +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/api/ConflictAPI.java b/src/main/java/xyz/alexcrea/cuanvil/api/ConflictAPI.java new file mode 100644 index 0000000..fe2715e --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/api/ConflictAPI.java @@ -0,0 +1,196 @@ +package xyz.alexcrea.cuanvil.api; + +import io.delilaheve.CustomAnvil; +import org.bukkit.NamespacedKey; +import org.bukkit.configuration.file.FileConfiguration; +import org.jetbrains.annotations.NotNull; +import xyz.alexcrea.cuanvil.config.ConfigHolder; +import xyz.alexcrea.cuanvil.dependency.DependencyManager; +import xyz.alexcrea.cuanvil.group.EnchantConflictGroup; +import xyz.alexcrea.cuanvil.gui.config.global.EnchantConflictGui; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Custom Anvil api for conflict registry. + */ +@SuppressWarnings("unused") +public class ConflictAPI { + + private ConflictAPI() { + } + + private static Object saveChangeTask = null; + private static Object reloadChangeTask = null; + + /** + * Write and add a conflict. + * Will not write the conflict if it already exists. + * Will not be successful if the conflict is empty. + * + * @param builder The conflict builder to be based on + * @return True if successful. + */ + public static boolean addConflict(@NotNull ConflictBuilder builder) { + return addConflict(builder, false); + } + + /** + * Write and add a conflict. + * Will not write the conflict if it already exists. + * Will not be successful if the conflict is empty. + * + * @param builder The conflict builder to be based on + * @param overrideDeleted If we should write even if the conflict was previously deleted. + * @return True if successful. + */ + public static boolean addConflict(@NotNull ConflictBuilder builder, boolean overrideDeleted) { + FileConfiguration config = ConfigHolder.CONFLICT_HOLDER.getConfig(); + + // Test if conflict can be added + if (!overrideDeleted && ConfigHolder.CONFLICT_HOLDER.isDeleted(builder.getName())) return false; + if (config.contains(builder.getName())) return false; + + if (!writeConflict(builder, false)) return false; + + EnchantConflictGroup conflict = builder.build(); + // Register conflict + ConfigHolder.CONFLICT_HOLDER.getConflictManager().addConflict(conflict); + + // Add conflict to gui + EnchantConflictGui conflictGui = EnchantConflictGui.getCurrentInstance(); + if (conflictGui != null) conflictGui.updateValueForGeneric(conflict, true); + + return true; + } + + /** + * Write a conflict to the config file and plan an update of conflicts. + *

+ * You may want to use {@link #addConflict(ConflictBuilder)} instead as it is more performance in most case as this function will reload every conflict. + * + * @param builder the builder + * @return true if was written successfully. + */ + public static boolean writeConflict(@NotNull ConflictBuilder builder) { + return writeConflict(builder, true); + } + + /** + * Write a conflict to the config file. + *

+ * You should use {@link #addConflict(ConflictBuilder)} or {@link #writeConflict(ConflictBuilder)} instead + * + * @param builder The builder + * @param updatePlanned If we should plan a global update for conflicts + * @return true if was written successfully. + */ + public static boolean writeConflict(@NotNull ConflictBuilder builder, boolean updatePlanned) { + FileConfiguration config = ConfigHolder.CONFLICT_HOLDER.getConfig(); + + String name = builder.getName(); + if (name.contains(".")) { + CustomAnvil.instance.getLogger().warning("Conflict " + name + " contain \".\" in its name but should not. this conflict is ignored."); + logConflictOrigin(builder); + return false; + } + + String basePath = name + "."; + + List enchantments = extractEnchantments(builder); + List excludedGroups = new ArrayList<>(builder.getExcludedGroupNames()); + if (!enchantments.isEmpty()) config.set(basePath + "enchantments", enchantments); + if (!excludedGroups.isEmpty()) config.set(basePath + "notAffectedGroups", excludedGroups); + if (builder.getMaxBeforeConflict() > 0) + config.set(basePath + "maxEnchantmentBeforeConflict", builder.getMaxBeforeConflict()); + + if (!config.isConfigurationSection(name)) return false; + + prepareSaveTask(); + if (updatePlanned) prepareUpdateTask(); + + return true; + } + + /** + * Extract every enchantment names from a builder. + * + * @param builder The builder storing the enchantments + * @return Builder's stored enchantment. + */ + @NotNull + private static List extractEnchantments(@NotNull ConflictBuilder builder) { + List result = new ArrayList<>(builder.getEnchantmentNames()); + for (NamespacedKey enchantmentKey : builder.getEnchantmentKeys()) { + result.add(enchantmentKey.toString()); + } + + return result; + } + + /** + * Remove a conflict. + * + * @param conflict The conflict to remove + * @return True if successful. + */ + public static boolean removeConflict(@NotNull EnchantConflictGroup conflict) { + // Remove from registry + ConfigHolder.CONFLICT_HOLDER.getConflictManager().removeConflict(conflict); + + // Delete and save to file + ConfigHolder.CONFLICT_HOLDER.delete(conflict.getName()); + prepareSaveTask(); + + // Remove from gui + EnchantConflictGui conflictGui = EnchantConflictGui.getCurrentInstance(); + if (conflictGui != null) conflictGui.removeGeneric(conflict); + + return true; + } + + /** + * Prepare a task to save conflict configuration. + */ + private static void prepareSaveTask() { + if (saveChangeTask != null) return; + + saveChangeTask = DependencyManager.scheduler.scheduleGlobally(CustomAnvil.instance, () -> { + ConfigHolder.CONFLICT_HOLDER.saveToDisk(true); + saveChangeTask = null; + }); + } + + /** + * Prepare a task to reload every conflict. + */ + private static void prepareUpdateTask() { + if (reloadChangeTask != null) return; + + reloadChangeTask = DependencyManager.scheduler.scheduleGlobally(CustomAnvil.instance, () -> { + ConfigHolder.CONFLICT_HOLDER.reload(); + EnchantConflictGui conflictGui = EnchantConflictGui.getCurrentInstance(); + if (conflictGui != null) conflictGui.reloadValues(); + + reloadChangeTask = null; + }); + } + + static void logConflictOrigin(@NotNull ConflictBuilder builder) { + CustomAnvil.instance.getLogger().warning("Conflict " + builder.getName() + " came from " + builder.getSourceName() + "."); + } + + /** + * Get every registered conflict. + * + * @return An immutable collection of conflict. + */ + @NotNull + public static List getRegisteredConflict() { + List mutableList = ConfigHolder.CONFLICT_HOLDER.getConflictManager().getConflictList(); + return Collections.unmodifiableList(mutableList); + } + +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/api/ConflictBuilder.java b/src/main/java/xyz/alexcrea/cuanvil/api/ConflictBuilder.java new file mode 100644 index 0000000..1460766 --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/api/ConflictBuilder.java @@ -0,0 +1,459 @@ +package xyz.alexcrea.cuanvil.api; + +import io.delilaheve.CustomAnvil; +import org.bukkit.NamespacedKey; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import xyz.alexcrea.cuanvil.config.ConfigHolder; +import xyz.alexcrea.cuanvil.enchant.CAEnchantment; +import xyz.alexcrea.cuanvil.group.*; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +//TODO add conflict after level +/** + * A Builder for material conflict. + */ +@SuppressWarnings("unused") +public class ConflictBuilder { + + private final @Nullable Plugin source; + private @NotNull String name; + + private final @NotNull Set enchantmentNames; + private final @NotNull Set enchantmentKeys; + + private final @NotNull Set excludedGroupNames; + + private int maxBeforeConflict; + + /** + * Instantiates a new Conflict builder. + * + * @param name The conflict name + * @param maxBeforeConflict Maximum number of conflicting enchantment before conflict is active + * @param source The conflict source + */ + public ConflictBuilder(@NotNull String name, int maxBeforeConflict, @Nullable Plugin source) { + this.source = source; + this.name = name; + + this.enchantmentNames = new HashSet<>(); + this.enchantmentKeys = new HashSet<>(); + + this.excludedGroupNames = new HashSet<>(); + + this.maxBeforeConflict = maxBeforeConflict; + } + + /** + * Instantiates a new Conflict builder. + * + * @param name The conflict name + * @param source The conflict source + */ + public ConflictBuilder(@NotNull String name, @Nullable Plugin source) { + this(name, 0, source); + } + + /** + * Instantiates a new Conflict builder. + * + * @param name The conflict name + */ + public ConflictBuilder(@NotNull String name) { + this(name, null); + } + + /** + * Gets conflict source. + * + * @return The conflict source. + */ + @Nullable + public Plugin getSource() { + return source; + } + + /** + * Gets conflict source name. + * + * @return The conflict source name. + */ + @NotNull + public String getSourceName() { + if (source == null) return "an unknown source"; + + return source.getName(); + } + + /** + * Gets conflict name. + * + * @return The conflict name. + */ + @NotNull + public String getName() { + return name; + } + + /** + * Gets stored conflicting enchantment names. + * + * @return The stored enchantment names. + */ + @NotNull + public Set getEnchantmentNames() { + return enchantmentNames; + } + + /** + * Gets stored conflicting enchantment keys. + * + * @return The stored enchantment keys. + */ + @NotNull + public Set getEnchantmentKeys() { + return enchantmentKeys; + } + + /** + * Gets stored excluded group names. + * + * @return The stored group names. + */ + @NotNull + public Set getExcludedGroupNames() { + return excludedGroupNames; + } + + /** + * Gets maximum number of conflicting enchantment before conflict is active. + *

+ * This value represent how many enchantment contained on this conflict can be applied to before conflict is considered active. + * That mean new enchantment will not be able to be added to the item and present enchantment will not have its level upgraded. + *

+ * In vanilla. material restriction have this value set to 0 and enchantment conflict set to 1. + * + * @return the max number of conflicting enchantment before conflict. 0 by default. + */ + public int getMaxBeforeConflict() { + return maxBeforeConflict; + } + + /** + * Sets conflict name. + * + * @param name The name + * @return This conflict builder instance. + */ + public ConflictBuilder setName(String name) { + this.name = name; + return this; + } + + /** + * Sets maximum number of conflicting enchantment before conflict is active. + *

+ * This value represent how many enchantment contained on this conflict can be applied to before conflict is considered active. + * That mean new enchantment will not be able to be added to the item and present enchantment will not have its level upgraded. + *

+ * In vanilla. material restriction have this value set to 0 and enchantment conflict set to 1. + * + * @param maxBeforeConflict The max before conflict + * @return This conflict builder instance. + */ + public ConflictBuilder setMaxBeforeConflict(int maxBeforeConflict) { + this.maxBeforeConflict = maxBeforeConflict; + return this; + } + + /** + * Add a conflicting enchantment by name. + * + * @param enchantmentName The enchantment name + * @return This conflict builder instance. + */ + @NotNull + public ConflictBuilder addEnchantment(@NotNull String enchantmentName) { + enchantmentNames.add(enchantmentName); + return this; + } + + /** + * Add a conflicting enchantment by key. + * + * @param enchantmentKey The enchantment key + * @return This conflict builder instance. + */ + @NotNull + public ConflictBuilder addEnchantment(@NotNull NamespacedKey enchantmentKey) { + enchantmentKeys.add(enchantmentKey); + return this; + } + + /** + * Add a conflicting enchantment by instance. + * + * @param enchantment The enchantment + * @return This conflict builder instance. + */ + @NotNull + public ConflictBuilder addEnchantment(@NotNull CAEnchantment enchantment) { + addEnchantment(enchantment.getKey()); + return this; + } + + /** + * Remove conflicting enchantment by name. + * + * @param enchantmentName The enchantment name + * @return This conflict builder instance. + */ + @NotNull + public ConflictBuilder removeEnchantment(@NotNull String enchantmentName) { + enchantmentNames.remove(enchantmentName); + return this; + } + + /** + * Remove conflicting enchantment by key. + * + * @param enchantmentKey The enchantment key + * @return This conflict builder instance. + */ + @NotNull + public ConflictBuilder removeEnchantment(@NotNull NamespacedKey enchantmentKey) { + enchantmentKeys.remove(enchantmentKey); + return removeEnchantment(enchantmentKey.getKey()); + } + + /** + * Remove enchantment by instance. + * + * @param enchantment The enchantment + * @return This conflict builder instance. + */ + @NotNull + public ConflictBuilder removeEnchantment(@NotNull CAEnchantment enchantment) { + return removeEnchantment(enchantment.getKey()); + } + + /** + * Add an excluded group by name. + *

+ * If left item of an anvil craft is included on one of the excluded group it will ignore this conflict. + *

+ * This allows to create conflict only for some item. Material restriction can be written like that. + *

+ * For example: If we exclude a material group containing every pickaxe and add efficiency enchantment + * with {@link #setMaxBeforeConflict(int) maxBeforeConflict} set to 0. + * Then only pickaxe will be able to have efficiency. + * + * @param groupName The group name + * @return This conflict builder instance. + */ + @NotNull + public ConflictBuilder addExcludedGroup(@NotNull String groupName) { + excludedGroupNames.add(groupName); + return this; + } + + /** + * Add an excluded group by instance. + *

+ * If left item of an anvil craft is included on one of the excluded group it will ignore this conflict. + *

+ * This allows to create conflict only for some item. Material restriction can be written like that. + *

+ * For example: If we exclude a material group containing every pickaxe and add efficiency enchantment + * with {@link #setMaxBeforeConflict(int) maxBeforeConflict} set to 0. + * Then only pickaxe will be able to have efficiency. + * + * @param group The group + * @return this conflict builder instance. + */ + @NotNull + public ConflictBuilder addExcludedGroup(@NotNull AbstractMaterialGroup group) { + return addExcludedGroup(group.getName()); + } + + /** + * Remove an excluded group by name. + *

+ * If left item of an anvil craft is included on one of the excluded group it will ignore this conflict. + *

+ * This allows to create conflict only for some item. Material restriction can be written like that. + *

+ * For example: If we exclude a material group containing every pickaxe and add efficiency enchantment + * with {@link #setMaxBeforeConflict(int) maxBeforeConflict} set to 0. + * Then only pickaxe will be able to have efficiency. + * + * @param groupName The group name + * @return This conflict builder instance. + */ + @NotNull + public ConflictBuilder removeExcludedGroup(@NotNull String groupName) { + excludedGroupNames.remove(groupName); + return this; + } + + /** + * Remove an excluded group by instance. + *

+ * If left item of an anvil craft is included on one of the excluded group it will ignore this conflict. + *

+ * This allows to create conflict only for some item. Material restriction can be written like that. + *

+ * For example: If we exclude a material group containing every pickaxe and add efficiency enchantment + * with {@link #setMaxBeforeConflict(int) maxBeforeConflict} set to 0. + * Then only pickaxe will be able to have efficiency. + * + * @param group The group + * @return This conflict builder instance. + */ + @NotNull + public ConflictBuilder removeExcludedGroup(@NotNull AbstractMaterialGroup group) { + return removeExcludedGroup(group.getName()); + } + + /** + * Copy this conflict builder. + * + * @return A copy of this conflict builder. + */ + @NotNull + public ConflictBuilder copy() { + ConflictBuilder copy = new ConflictBuilder(this.name, this.source); + + copy.setMaxBeforeConflict(this.maxBeforeConflict); + + // Set Enchantments + for (NamespacedKey key : this.enchantmentKeys) { + copy.addEnchantment(key); + } + for (String enchantName : this.enchantmentNames) { + copy.addEnchantment(enchantName); + } + + // Set Groups + for (String groupName : this.excludedGroupNames) { + copy.addExcludedGroup(groupName); + } + + return copy; + } + + /** + * Build a new Enchant conflict group by this builder. + * + * @return An Enchant conflict group with this builder parameters. + */ + public EnchantConflictGroup build() { + AbstractMaterialGroup materials = extractGroups(); + EnchantConflictGroup conflict = new EnchantConflictGroup(getName(), materials, getMaxBeforeConflict()); + appendEnchantments(conflict); + + return conflict; + } + + /** + * Register this conflict if not yet registered. + * Equivalent to {@link ConflictAPI#addConflict(ConflictBuilder, boolean) ConflictAPI.addConflict(this, true)}} + * + * @return True if successful. + */ + public boolean registerIfAbsent() { + return ConflictAPI.addConflict(this, true); + } + + /** + * Register this conflict if not yet registered or deleted. + * Equivalent to {@link ConflictAPI#addConflict(ConflictBuilder) ConflictAPI.addConflict(this)} + * + * @return True if successful. + */ + public boolean registerIfNew() { + return ConflictAPI.addConflict(this); + } + + /** + * Append builders stored enchantments into conflict. + * + * @param conflict The conflict target + */ + protected void appendEnchantments(@NotNull EnchantConflictGroup conflict) { + for (String enchantmentName : getEnchantmentNames()) { + if (appendEnchantments(conflict, EnchantmentApi.getListByName(enchantmentName)) == 0) { + CustomAnvil.instance.getLogger().warning("Could not find enchantment " + enchantmentName + " for conflict " + getName()); + ConflictAPI.logConflictOrigin(this); + } + } + for (NamespacedKey enchantmentKey : getEnchantmentKeys()) { + if (!appendEnchantment(conflict, EnchantmentApi.getByKey(enchantmentKey))) { + CustomAnvil.instance.getLogger().warning("Could not find enchantment " + enchantmentKey + " for conflict " + getName()); + ConflictAPI.logConflictOrigin(this); + } + } + } + + /** + * Append an enchantment. + * + * @param conflict The conflict target + * @param enchantment The enchantment + * @return True if successful. + */ + protected static boolean appendEnchantment(@NotNull EnchantConflictGroup conflict, @Nullable CAEnchantment enchantment) { + if (enchantment == null) + return false; + conflict.addEnchantment(enchantment); + return true; + } + + /** + * Append a list of enchantments. + * + * @param conflict The conflict target + * @param enchantments List of enchantment to add + * @return Number of enchantment added + */ + protected static int appendEnchantments(@NotNull EnchantConflictGroup conflict, @NotNull List enchantments) { + int numberValid = 0; + for (CAEnchantment enchantment : enchantments) { + if (appendEnchantment(conflict, enchantment)) { + numberValid++; + } + } + + return numberValid; + } + + /** + * Extract group abstract material group. + * + * @return The abstract material group from the builder. + */ + protected AbstractMaterialGroup extractGroups() { + ItemGroupManager itemGroupManager = ConfigHolder.ITEM_GROUP_HOLDER.getItemGroupsManager(); + IncludeGroup group = new IncludeGroup(EnchantConflictManager.DEFAULT_GROUP_NAME); + + for (String groupName : getExcludedGroupNames()) { + AbstractMaterialGroup materialGroup = itemGroupManager.get(groupName); + + if (materialGroup == null) { + CustomAnvil.instance.getLogger().warning("Material group " + groupName + " do not exist but is ask by conflict " + getName()); + ConflictAPI.logConflictOrigin(this); + continue; + } + + group.addToPolicy(materialGroup); + } + + return group; + } + +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/api/CustomAnvilRecipeApi.java b/src/main/java/xyz/alexcrea/cuanvil/api/CustomAnvilRecipeApi.java new file mode 100644 index 0000000..8f80aa3 --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/api/CustomAnvilRecipeApi.java @@ -0,0 +1,126 @@ +package xyz.alexcrea.cuanvil.api; + +import io.delilaheve.CustomAnvil; +import org.bukkit.configuration.file.FileConfiguration; +import org.jetbrains.annotations.NotNull; +import xyz.alexcrea.cuanvil.config.ConfigHolder; +import xyz.alexcrea.cuanvil.dependency.DependencyManager; +import xyz.alexcrea.cuanvil.gui.config.global.CustomRecipeConfigGui; +import xyz.alexcrea.cuanvil.recipe.AnvilCustomRecipe; + +import java.util.Collections; +import java.util.List; + +/** + * Custom Anvil api for custom anvil recipes. + */ +@SuppressWarnings("unused") +public class CustomAnvilRecipeApi { + + private CustomAnvilRecipeApi(){} + + private static Object saveChangeTask = null; + + /** + * Write and add a custom anvil recipe. + * Will not write the recipe if it already exists. + * + * @param builder The recipe builder to be based on + * @return True if successful. + */ + public static boolean addRecipe(@NotNull AnvilRecipeBuilder builder){ + return addRecipe(builder, false); + } + + /** + * Write and add a custom anvil recipe. + * Will not write the recipe if it already exists. + * + * @param builder The recipe builder to be based on + * @param overrideDeleted If we should write even if the recipe was previously deleted. + * @return True if successful. + */ + public static boolean addRecipe(@NotNull AnvilRecipeBuilder builder, boolean overrideDeleted){ + FileConfiguration config = ConfigHolder.CUSTOM_RECIPE_HOLDER.getConfig(); + String name = builder.getName(); + + if(!overrideDeleted && ConfigHolder.CUSTOM_RECIPE_HOLDER.isDeleted(builder.getName())) return false; + if(config.contains(builder.getName())) return false; + + if(builder.getName().contains(".")) { + CustomAnvil.instance.getLogger().warning("Custom anvil recipe " + name + " contain \".\" in its name but should not. this recipe is ignored."); + return false; + } + + AnvilCustomRecipe recipe = builder.build(); + if(recipe == null){ + CustomAnvil.instance.getLogger().warning("Custom anvil recipe " + name + " could not be parsed."); + if(builder.getLeftItem() == null){ + CustomAnvil.instance.getLogger().warning("It look like left item of the recipe is null."); + } + if(builder.getResultItem() == null){ + CustomAnvil.instance.getLogger().warning("It look like result item of the recipe is null."); + } + return false; + } + + // Add to registry + ConfigHolder.CUSTOM_RECIPE_HOLDER.getRecipeManager().cleanAddNew(recipe); + + // Save to file + recipe.saveToFile(false, false); + prepareSaveTask(); + + // Add from gui + CustomRecipeConfigGui recipeConfigGui = CustomRecipeConfigGui.getCurrentInstance(); + if(recipeConfigGui != null) recipeConfigGui.updateValueForGeneric(recipe, true); + + return true; + } + + // TODO remove by name and/or by builder (as name is keept) (and maybe create a get by name) + /** + * Remove a custom anvil recipe. + * + * @param recipe The recipe to remove + * @return True if successful. + */ + public static boolean removeRecipe(@NotNull AnvilCustomRecipe recipe){ + // Remove from registry + boolean result = ConfigHolder.CUSTOM_RECIPE_HOLDER.getRecipeManager().cleanRemove(recipe); + if(!result) return false; + + // Delete and save to file + ConfigHolder.CUSTOM_RECIPE_HOLDER.delete(recipe.getName()); + prepareSaveTask(); + + // Remove from gui + CustomRecipeConfigGui recipeConfigGui = CustomRecipeConfigGui.getCurrentInstance(); + if(recipeConfigGui != null) recipeConfigGui.removeGeneric(recipe); + + return true; + } + + /** + * Prepare a task to save custom recipe configuration. + */ + private static void prepareSaveTask() { + if(saveChangeTask != null) return; + + saveChangeTask = DependencyManager.scheduler.scheduleGlobally(CustomAnvil.instance, ()->{ + ConfigHolder.CONFLICT_HOLDER.saveToDisk(true); + saveChangeTask = null; + }); + } + + /** + * Get every registered recipes. + * @return An immutable collection of recipes. + */ + @NotNull + public static List getRegisteredRecipes(){ + List mutableList = ConfigHolder.CUSTOM_RECIPE_HOLDER.getRecipeManager().getRecipeList(); + return Collections.unmodifiableList(mutableList); + } + +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/api/EnchantmentApi.java b/src/main/java/xyz/alexcrea/cuanvil/api/EnchantmentApi.java new file mode 100644 index 0000000..ac98225 --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/api/EnchantmentApi.java @@ -0,0 +1,235 @@ +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; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import xyz.alexcrea.cuanvil.config.ConfigHolder; +import xyz.alexcrea.cuanvil.dependency.DependencyManager; +import xyz.alexcrea.cuanvil.enchant.CAEnchantment; +import xyz.alexcrea.cuanvil.enchant.CAEnchantmentRegistry; +import xyz.alexcrea.cuanvil.enchant.EnchantmentRarity; +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.gui.config.global.EnchantCostConfigGui; +import xyz.alexcrea.cuanvil.gui.config.global.EnchantLimitConfigGui; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * Custom Anvil api for enchantment registry. + */ +@SuppressWarnings("unused") +public class EnchantmentApi { + + private static Object saveChangeTask = null; + + private EnchantmentApi() {} + + /** + * Register an enchantment. + * + * @param enchantment The enchantment to register + * @return True if successful. + */ + public static boolean registerEnchantment(@NotNull CAEnchantment enchantment){ + if(!CAEnchantmentRegistry.getInstance().register(enchantment)) return false; + + // Add enchantment to gui. + if(EnchantCostConfigGui.getInstance() != null){ + EnchantCostConfigGui.getInstance().updateValueForGeneric(enchantment, true); + } + if(EnchantLimitConfigGui.getInstance() != null){ + EnchantLimitConfigGui.getInstance().updateValueForGeneric(enchantment, true); + } + + // Write default if do not exist + writeDefaultConfig(enchantment, false); + + return true; + } + + /** + * Register an enchantment by minecraft registered enchantment instance. + * + * @param enchantment The enchantment to register + * @param defaultRarity The default rarity of the provided enchantment + * @return True if successful. + */ + public static boolean registerEnchantment(@NotNull Enchantment enchantment, @Nullable EnchantmentRarity defaultRarity){ + if(defaultRarity == null) + return registerEnchantment(new CABukkitEnchantment(enchantment)); + + return registerEnchantment(new CABukkitEnchantment(enchantment, defaultRarity)); + } + + /** + * Register an enchantment by minecraft registered enchantment instance. + *

+ * Please note that this function assume the provided enchantment is registered into minecraft registry. + * + * @param enchantment The enchantment to register + * @return True if successful. + */ + public static boolean registerEnchantment(@NotNull Enchantment enchantment){ + return registerEnchantment(new CABukkitEnchantment(enchantment)); + } + + /** + * Unregister an enchantment. + * + * @param enchantment The enchantment to unregister + * @return True if successful. + */ + public static boolean unregisterEnchantment(@Nullable CAEnchantment enchantment){ + // Remove from gui + if(EnchantCostConfigGui.getInstance() != null){ + EnchantCostConfigGui.getInstance().removeGeneric(enchantment); + } + if(EnchantLimitConfigGui.getInstance() != null){ + EnchantLimitConfigGui.getInstance().removeGeneric(enchantment); + } + + return CAEnchantmentRegistry.getInstance().unregister(enchantment); + } + + /** + * Unregister an enchantment by its key. + * + * @param key The enchantment key to unregister + * @return True if successful. + */ + public static boolean unregisterEnchantment(@NotNull NamespacedKey key){ + CAEnchantment enchantment = CAEnchantment.getByKey(key); + return unregisterEnchantment(enchantment); + } + + /** + * Unregister an enchantment by his bukkit enchantment. + * + * @param enchantment The enchantment to unregister + * @return True if successful. + */ + public static boolean unregisterEnchantment(@NotNull Enchantment enchantment){ + return unregisterEnchantment(enchantment.getKey()); + } + + /** + * Get by key an enchantment. + * + * @param key The key used to fetch + * @return The custom anvil enchantment of this key. null if not found. + */ + @Nullable + public static CAEnchantment getByKey(@NotNull NamespacedKey key){ + return CAEnchantment.getByKey(key); + } + + /** + * Get by name an enchantment. + * + * @param name The name used to fetch + * @return The custom anvil enchantment of this name. null if not found. + * @deprecated use {@link #getListByName(String)} + */ + @Deprecated(since = "1.6.3") + @Nullable + public static CAEnchantment getByName(@NotNull String name){ + return CAEnchantment.getByName(name); + } + + /** + * Get list of enchantment using the provided name. + * + * @param name The name used to fetch + * @return List of custom anvil enchantments of this name. May be empty if not found. + */ + public static List getListByName(@NotNull String name){ + return CAEnchantment.getListByName(name); + } + + /** + * Get every registered custom anvil enchantments. + * @return An immutable map of enchantment key as map key and custom anvil enchantment as value. + */ + @NotNull + public static Map getRegisteredEnchantments(){ + return Collections.unmodifiableMap(CAEnchantmentRegistry.getInstance().registeredEnchantments()); + } + + /** + * Write the default level and rarity configuration of the enchantment. + * @param enchantment The enchantment to write default configuration + * @param override If it should override old configuration + * @return Return false if override is false and a configuration exist. true otherwise. + */ + public static boolean writeDefaultConfig(CAEnchantment enchantment, boolean override){ + FileConfiguration config = ConfigHolder.DEFAULT_CONFIG.getConfig(); + + if(tryWriteDefaultConfig(config, enchantment, override)){ + prepareSaveTask(); + } + return true; + } + + private static boolean tryWriteDefaultConfig(FileConfiguration defaultConfig, CAEnchantment enchantment, boolean override) { + boolean hasChange = false; + + String levelPath = ConfigOptions.ENCHANT_LIMIT_ROOT + "." + enchantment.getKey(); + if(override || !defaultConfig.isSet(levelPath)){ + defaultConfig.set(levelPath, enchantment.defaultMaxLevel()); + hasChange = true; + } + + String basePath = ConfigOptions.ENCHANT_VALUES_ROOT + "." + enchantment.getKey(); + EnchantmentRarity rarity = enchantment.defaultRarity(); + + String itemPath = basePath + ".item"; + String bookPath = basePath + ".book"; + if(override || !defaultConfig.isSet(itemPath)){ + defaultConfig.set(itemPath, rarity.getItemValue()); + hasChange = true; + } + if(override || !defaultConfig.isSet(bookPath)){ + defaultConfig.set(bookPath, rarity.getBookValue()); + hasChange = true; + } + + return hasChange; + } + + /** + * Prepare a task to save custom recipe configuration. + */ + private static void prepareSaveTask() { + if(saveChangeTask != null) return; + + saveChangeTask = DependencyManager.scheduler.scheduleGlobally(CustomAnvil.instance, ()->{ + ConfigHolder.DEFAULT_CONFIG.saveToDisk(true); + saveChangeTask = null; + }); + } + + /** + * Add a bulk get operator. + * @param operation An optimised get enchantments operation + */ + public static void addBulkGet(@NotNull BulkGetEnchantOperation operation){ + CAEnchantmentRegistry.getInstance().getOptimisedGetOperators().add(operation); + } + + /** + * Add a bulk clean operator. + * @param operation An optimised clean enchantments operation + */ + public static void addBulkClean(@NotNull BulkCleanEnchantOperation operation){ + CAEnchantmentRegistry.getInstance().getOptimisedCleanOperators().add(operation); + } + +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/api/MaterialGroupApi.java b/src/main/java/xyz/alexcrea/cuanvil/api/MaterialGroupApi.java new file mode 100644 index 0000000..cd71c7a --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/api/MaterialGroupApi.java @@ -0,0 +1,251 @@ +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; +import xyz.alexcrea.cuanvil.config.ConfigHolder; +import xyz.alexcrea.cuanvil.dependency.DependencyManager; +import xyz.alexcrea.cuanvil.group.AbstractMaterialGroup; +import xyz.alexcrea.cuanvil.group.ExcludeGroup; +import xyz.alexcrea.cuanvil.group.IncludeGroup; +import xyz.alexcrea.cuanvil.group.ItemGroupManager; +import xyz.alexcrea.cuanvil.gui.config.global.GroupConfigGui; + +import java.util.*; + +/** + * Custom Anvil api for material group registry. + */ +@SuppressWarnings("unused") +public class MaterialGroupApi { + + private MaterialGroupApi() { + } + + private static Object saveChangeTask = null; + private static Object reloadChangeTask = null; + + /** + * Write and add a group. + * Will not write the group if it already exists. + * Will not be successful if the group is empty. + * + * @param group The group to add + * @return true if successful. + */ + public static boolean addMaterialGroup(@NotNull AbstractMaterialGroup group) { + return addMaterialGroup(group, false); + } + + /** + * Write and add a group. + * Will not write the group if it already exists. + * Will not be successful if the group is empty. + * + * @param group The group to add + * @param overrideDeleted If we should write even if the group was previously deleted. + * @return true if successful. + */ + public static boolean addMaterialGroup(@NotNull AbstractMaterialGroup group, boolean overrideDeleted) { + ItemGroupManager itemGroupManager = ConfigHolder.ITEM_GROUP_HOLDER.getItemGroupsManager(); + + // Test if it exists/existed + if (!overrideDeleted && ConfigHolder.ITEM_GROUP_HOLDER.isDeleted(group.getName())) return false; + if (itemGroupManager.get(group.getName()) != null) return false; + + // Add group + itemGroupManager.getGroupMap().put(group.getName(), group); + + if (!writeMaterialGroup(group, false)) return false; + + if (group instanceof IncludeGroup includeGroup) { + GroupConfigGui configGui = GroupConfigGui.getCurrentInstance(); + if (configGui != null) configGui.updateValueForGeneric(includeGroup, true); + } + + if (ConfigOptions.INSTANCE.getVerboseDebugLog()) { + CustomAnvil.instance.getLogger().info("Registered group " + group.getName()); + } + + return true; + } + + /** + * Write a material group to the config file and plan an update of groups. + *

+ * You may want to use {@link #addMaterialGroup(AbstractMaterialGroup)} instead as it is more performance in most case as this function will reload every conflict. + * + * @param group the group to write + * @return true if was written successfully. + */ + public static boolean writeMaterialGroup(@NotNull AbstractMaterialGroup group) { + return writeMaterialGroup(group, true); + } + + /** + * Write a material group to the config file. + *

+ * You should use {@link #addMaterialGroup(AbstractMaterialGroup)} or {@link #writeMaterialGroup(AbstractMaterialGroup)} instead + * + * @param group the group to write + * @param updatePlanned if we should plan a global update for material groups + * @return true if was written successfully. + */ + public static boolean writeMaterialGroup(@NotNull AbstractMaterialGroup group, boolean updatePlanned) { + String name = group.getName(); + if (name.contains(".")) { + CustomAnvil.instance.getLogger().warning("Group " + name + " contain . in its name but should not. this material group is ignored."); + return false; + } + + boolean changed; + if (group instanceof IncludeGroup includeGroup) { + changed = writeKnownGroup("include", includeGroup); + } else if (group instanceof ExcludeGroup 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); + } + if (!changed) return false; + + prepareSaveTask(); + if (updatePlanned) prepareUpdateTask(); + + return true; + } + + private static boolean writeKnownGroup(@NotNull String groupType, @NotNull AbstractMaterialGroup group) { + FileConfiguration config = ConfigHolder.ITEM_GROUP_HOLDER.getConfig(); + + String basePath = group.getName() + "."; + 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; + } + + config.set(basePath + ItemGroupManager.GROUP_TYPE_PATH, groupType); + return true; + } + + private static boolean writeUnknownGroup(@NotNull AbstractMaterialGroup group) { + FileConfiguration config = ConfigHolder.ITEM_GROUP_HOLDER.getConfig(); + + String basePath = group.getName() + "."; + Set materials = group.getMaterials(); + + if (materials.isEmpty()) return false; + + config.set(basePath + ItemGroupManager.GROUP_TYPE_PATH, "include"); + config.set(basePath + ItemGroupManager.MATERIAL_LIST_PATH, materialSetToStringList(materials)); + + return true; + } + + public static List materialSetToStringList(@NotNull Set materials) { + return materials.stream().map(NamespacedKey::toString).toList(); + } + + public static List materialGroupSetToStringList(@NotNull Set groups) { + return groups.stream().map(AbstractMaterialGroup::getName).toList(); + } + + /** + * Remove a material group. + * Caution ! It will not be removed from depending conflict or other material group at runtime. + * For that reason, it is not recommended to use this function. + * + * @param group The recipe to remove + * @return True if the group was present. + */ + public static boolean removeGroup(@NotNull AbstractMaterialGroup group) { + // Remove from registry + AbstractMaterialGroup removed = ConfigHolder.ITEM_GROUP_HOLDER.getItemGroupsManager().groupMap.remove(group.getName()); + if (removed == null) return false; + + // Delete and save to file + ConfigHolder.ITEM_GROUP_HOLDER.delete(group.getName()); + prepareSaveTask(); + + // Remove from gui + if (group instanceof IncludeGroup includeGroup) { + GroupConfigGui configGui = GroupConfigGui.getCurrentInstance(); + if (configGui != null) configGui.removeGeneric(includeGroup); + } + + return true; + } + + /** + * Prepare a task to reload every conflict. + */ + private static void prepareSaveTask() { + if (saveChangeTask != null) return; + + saveChangeTask = DependencyManager.scheduler.scheduleGlobally(CustomAnvil.instance, () -> { + ConfigHolder.ITEM_GROUP_HOLDER.saveToDisk(true); + saveChangeTask = null; + }); + } + + /** + * Prepare a task to save configuration. + */ + private static void prepareUpdateTask() { + if (reloadChangeTask != null) return; + + reloadChangeTask = DependencyManager.scheduler.scheduleGlobally(CustomAnvil.instance, () -> { + ConfigHolder.ITEM_GROUP_HOLDER.reload(); + + GroupConfigGui configGui = GroupConfigGui.getCurrentInstance(); + if (configGui != null) configGui.reloadValues(); + + reloadChangeTask = null; + }); + + } + + /** + * Get by name a group. + * + * @param groupName the group name used to fetch + * @return the abstract group of this name. null if not found. + */ + @Nullable + public static AbstractMaterialGroup getGroup(@NotNull String groupName) { + return ConfigHolder.ITEM_GROUP_HOLDER.getItemGroupsManager().get(groupName); + } + + /** + * Get every registered material groups. + * + * @return An immutable map of group name as its key and group as mapped value. + */ + @NotNull + public static Map getRegisteredGroups() { + return Collections.unmodifiableMap(ConfigHolder.ITEM_GROUP_HOLDER.getItemGroupsManager().getGroupMap()); + } + +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/api/UnitRepairApi.java b/src/main/java/xyz/alexcrea/cuanvil/api/UnitRepairApi.java new file mode 100644 index 0000000..bc50c16 --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/api/UnitRepairApi.java @@ -0,0 +1,218 @@ +package xyz.alexcrea.cuanvil.api; + +import io.delilaheve.CustomAnvil; +import kotlin.Triple; +import org.bukkit.Material; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; +import org.jetbrains.annotations.NotNull; +import xyz.alexcrea.cuanvil.config.ConfigHolder; +import xyz.alexcrea.cuanvil.dependency.DependencyManager; +import xyz.alexcrea.cuanvil.gui.config.global.UnitRepairConfigGui; +import xyz.alexcrea.cuanvil.gui.config.list.MappedGuiListConfigGui; +import xyz.alexcrea.cuanvil.gui.config.list.UnitRepairElementListGui; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Custom Anvil api for unit repair. + */ +@SuppressWarnings("unused") +public class UnitRepairApi { + + private UnitRepairApi(){} + + private static Object saveChangeTask = null; + + /** + * Write and add a custom anvil unit repair recipe. + * Will not write the recipe if it already exists or was deleted. + * Set the value to minecraft default value (0.25 = 25%) + * + * @param unit The unit material used to repair the bellow item. + * @param repairable The item to be repaired. + * @return true if successful. + */ + public static boolean addUnitRepair(@NotNull Material unit, @NotNull Material repairable){ + return addUnitRepair(unit, repairable, 0.25, false); + } + + /** + * Write and add a custom anvil unit repair recipe. + * Will not write the recipe if it already exists or was deleted. + * + * @param unit The unit material used to repair the bellow item. + * @param repairable The item to be repaired. + * @param value The amount to be repaired by every unit. (1% = 0.01) + * @return true if successful. + */ + public static boolean addUnitRepair(@NotNull Material unit, @NotNull Material repairable, double value){ + return addUnitRepair(unit, repairable, value, false); + } + + /** + * Write and add a custom anvil unit repair recipe. + * Will not write the recipe if it already exists. + * + * @param unit The unit material used to repair the bellow item. + * @param repairable The item to be repaired. + * @param value The amount to be repaired by every unit. (1% = 0.01) + * @param overrideDeleted If we should write even if the recipe was previously deleted. + * @return true if successful. + */ + public static boolean addUnitRepair(@NotNull Material unit, @NotNull Material repairable, double value, boolean overrideDeleted){ + FileConfiguration config = ConfigHolder.UNIT_REPAIR_HOLDER.getConfig(); + String path = unit.name().toLowerCase() + "." + repairable.name().toLowerCase(); + + if(!overrideDeleted && ConfigHolder.UNIT_REPAIR_HOLDER.isDeleted(path)) return false; + if(config.contains(path)) return false; + + // Set unit repair + return setUnitRepair(unit, repairable, value); + } + + /** + * Write and add a custom anvil unit repair recipe. + * Do not check if it previously existed or exist. + * + * @param unit The unit material used to repair the bellow item. + * @param repairable The item to be repaired. + * @param value The amount to be repaired by every unit. (1% = 0.01) + * @return true if successful. + */ + public static boolean setUnitRepair(@NotNull Material unit, @NotNull Material repairable, double value){ + FileConfiguration config = ConfigHolder.UNIT_REPAIR_HOLDER.getConfig(); + + String repairableName = repairable.name().toLowerCase(); + String path = unit.name().toLowerCase() + "." + repairableName; + + // Add to config then prepare save + config.set(path, value); + prepareSaveTask(); + + // Add to gui + UnitRepairConfigGui repairConfigGui = UnitRepairConfigGui.getCurrentInstance(); + if(repairConfigGui != null) { + UnitRepairElementListGui elementGui = repairConfigGui.getInstanceOrCreate(unit).getStored(); + + if(elementGui != null) elementGui.updateValueForGeneric(repairableName, true); + repairConfigGui.updateValueForGeneric(unit, true); + } + + return true; + } + + /** + * Remove a custom anvil unit repair recipe. + * + * @param unit The unit material used to repair the bellow item. + * @param repairable The item used to be repaired. + * @return true if successful. + */ + public static boolean removeUnitRepair(@NotNull Material unit, @NotNull Material repairable){ + // Delete every possible variation and save to file + String unitName = unit.name(); + String repairableName = repairable.name(); + + FileConfiguration config = ConfigHolder.UNIT_REPAIR_HOLDER.getConfig(); + config.set(unitName.toLowerCase() + "." + repairableName.toUpperCase(), null); + config.set(unitName.toUpperCase() + "." + repairableName.toLowerCase(), null); + config.set(unitName.toUpperCase() + "." + repairableName.toUpperCase(), null); + config.set(unitName.toLowerCase() + "." + repairableName.toLowerCase(), null); + + // Test if it was the last value of this section + boolean lastValue = false; + if(config.isConfigurationSection(unitName.toLowerCase())) { + ConfigurationSection section = config.getConfigurationSection(unitName.toLowerCase()); + + if(section != null && section.getKeys(false).isEmpty()) { + lastValue = true; + config.set(unitName.toLowerCase(), null); + } + + } else if (config.isConfigurationSection(unitName.toUpperCase())) { + ConfigurationSection section = config.getConfigurationSection(unitName.toUpperCase()); + if(section != null && section.getKeys(false).isEmpty()) { + lastValue = true; + config.set(unitName.toUpperCase(), null); + } + + } else lastValue = true; + + + // We only need to "delete" as the lower case to be counted as deleted + ConfigHolder.UNIT_REPAIR_HOLDER.delete(unitName.toLowerCase() + "." + repairableName.toLowerCase()); + prepareSaveTask(); + + // Remove from gui + UnitRepairConfigGui repairConfigGui = UnitRepairConfigGui.getCurrentInstance(); + if(repairConfigGui != null) { + UnitRepairElementListGui elementGui = repairConfigGui.getInstanceOrCreate(unit).getStored(); + + if(elementGui != null) elementGui.removeGeneric(repairableName); + if(lastValue){ + repairConfigGui.removeGeneric(unit); + } + } + + return true; + } + + /** + * Prepare a task to save custom unit repair recipe configuration. + */ + private static void prepareSaveTask() { + if(saveChangeTask != null) return; + + saveChangeTask = DependencyManager.scheduler.scheduleGlobally(CustomAnvil.instance, ()->{ + ConfigHolder.UNIT_REPAIR_HOLDER.saveToDisk(true); + saveChangeTask = null; + }); + } + + /** + * Get every unit repair recipes. + * @return An immutable collection of unit repair recipes. + *

+ * Each element of the provided triple represent a part of the recipe + *

    + *
  • First object is the unit material used to repair the bellow item. + *
  • Second object is the item to be repaired. + *
  • Last object is the amount to be repaired by every unit. (1% = 0.01) + *
+ */ + @NotNull + public static List> getUnitRepairs(){ + List> mutableList = new ArrayList<>(); + + FileConfiguration config = ConfigHolder.UNIT_REPAIR_HOLDER.getConfig(); + for (String unitKey : config.getKeys(false)) { + // Test if config section exist + if(!config.isConfigurationSection(unitKey)) continue; + + // Test if unit is a material + Material unit = Material.getMaterial(unitKey.toUpperCase()); + if(unit == null) continue; + + // Iterate over reparable items + ConfigurationSection section = config.getConfigurationSection(unitKey); + for (String repairableKey : section.getKeys(false)) { + // Test if value section exist + if(!section.isDouble(repairableKey)) continue; + + // Test if repairable is valid a material + Material repairable = Material.getMaterial(repairableKey.toUpperCase()); + if(repairable == null) continue; + + // Add the values + mutableList.add(new Triple<>(unit, repairable, section.getDouble(repairableKey))); + + } + } + + return Collections.unmodifiableList(mutableList); + } + +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/api/event/CAConfigReadyEvent.java b/src/main/java/xyz/alexcrea/cuanvil/api/event/CAConfigReadyEvent.java new file mode 100644 index 0000000..67d27a8 --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/api/event/CAConfigReadyEvent.java @@ -0,0 +1,36 @@ +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(); + + public static HandlerList getHandlerList() { + return HANDLERS; + } + + @Override + public HandlerList getHandlers() { + return HANDLERS; + } + +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/api/event/CAEnchantRegistryReadyEvent.java b/src/main/java/xyz/alexcrea/cuanvil/api/event/CAEnchantRegistryReadyEvent.java new file mode 100644 index 0000000..3ffe372 --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/api/event/CAEnchantRegistryReadyEvent.java @@ -0,0 +1,29 @@ +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(); + + public static HandlerList getHandlerList() { + return HANDLERS; + } + + @Override + public HandlerList getHandlers() { + return HANDLERS; + } +} 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 8999b8b..f6a7e80 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/config/ConfigHolder.java +++ b/src/main/java/xyz/alexcrea/cuanvil/config/ConfigHolder.java @@ -4,13 +4,18 @@ import com.google.common.io.Files; import io.delilaheve.CustomAnvil; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; +import org.jetbrains.annotations.NotNull; +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; +import java.util.logging.Level; +@SuppressWarnings("unused") public abstract class ConfigHolder { // Available configuration: @@ -20,21 +25,38 @@ public abstract class ConfigHolder { public static UnitRepairHolder UNIT_REPAIR_HOLDER; public static CustomAnvilCraftHolder CUSTOM_RECIPE_HOLDER; - public static boolean loadConfig() { + /** + * Load default configuration. + * @return True if successful. + */ + public static boolean loadDefaultConfig() { DEFAULT_CONFIG = new DefaultConfigHolder(); + + return DEFAULT_CONFIG.reloadFromDisk(true); + } + + /** + * Load non default configuration. + * @return True if successful. + */ + public static boolean loadNonDefaultConfig() { ITEM_GROUP_HOLDER = new ItemGroupConfigHolder(); CONFLICT_HOLDER = new ConflictConfigHolder(); UNIT_REPAIR_HOLDER = new UnitRepairHolder(); CUSTOM_RECIPE_HOLDER = new CustomAnvilCraftHolder(); - return reloadAllFromDisk(true); + return removeNonDefaultFromDisk(true); } public static boolean reloadAllFromDisk(boolean hardfail) { - boolean sucess = DEFAULT_CONFIG.reloadFromDisk(hardfail); if (!sucess) return false; - sucess = ITEM_GROUP_HOLDER.reloadFromDisk(hardfail); + + return removeNonDefaultFromDisk(hardfail); + } + + private static boolean removeNonDefaultFromDisk(boolean hardfail){ + boolean sucess = ITEM_GROUP_HOLDER.reloadFromDisk(hardfail); if (!sucess) return false; sucess = CONFLICT_HOLDER.reloadFromDisk(hardfail); if (!sucess) return false; @@ -123,7 +145,8 @@ public abstract class ConfigHolder { Files.copy(base, firstBackup); sufficientSuccess = true; } catch (IOException e) { - e.printStackTrace(); + CustomAnvil.instance.getLogger().log(Level.WARNING, "Could not copy backup saving config " + base.getName(), e); + MetricsUtil.INSTANCE.trackError(e); } } // save last backup @@ -183,16 +206,129 @@ public abstract class ConfigHolder { YamlConfiguration configuration = CustomAnvil.instance.reloadResource( getConfigFileName() + getConfigFileExtension(), hardFail); if (configuration == null) return false; + this.configuration = configuration; reload(); + return true; } } + public abstract static class DeletableResource extends ResourceConfigHolder{ + + private static final String DELETED_FOLDER_PATH = "deleted"; + + private final @NotNull File parent; + private final @NotNull File deletedConfigFile; + + private @Nullable YamlConfiguration deletedListConfig; + private DeletableResource(String resourceName) { + super(resourceName); + this.parent = new File(CustomAnvil.instance.getDataFolder(), DELETED_FOLDER_PATH); + this.deletedConfigFile = new File(this.parent, "deleted_" + resourceName + getConfigFileExtension()); + } + + @Override + public boolean reloadFromDisk(boolean hardFail) { + if(!super.reloadFromDisk(hardFail)) return false; + loadDeletedListFile(hardFail); + + return true; + } + + private void loadDeletedListFile(boolean hardFail){ + this.deletedListConfig = CustomAnvil.instance.reloadResource(this.deletedConfigFile, hardFail); + + } + + /** + * Test if the provided element was deleted. + * @param objectPath The object path to delete. + * @return True if successful. + */ + public boolean isDeleted(String objectPath){ + if(this.deletedListConfig == null) return false; + + return this.deletedListConfig.getBoolean(objectPath, false); + } + + /** + * Delete a certain object by its path. do not save the config. + * @param objectPath The object path to delete. + * @return True if successful. + */ + public boolean delete(String objectPath){ + return delete(objectPath, false, false); + } + + /** + * Delete a certain object by its path. + * @param objectPath The object path to delete. + * @param doSave If we should save the config after deleting. + * @param doBackup If we should create a backup. + * @return True if successful. + */ + public boolean delete(String objectPath, boolean doSave, boolean doBackup){ + // Create deleted list if it does not yet exist + if(this.deletedListConfig == null){ + this.parent.mkdirs(); + try { + 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); + + // Something was wrong somehow + if(this.deletedListConfig == null) return false; + } + + // Add to the deleted config + this.deletedListConfig.set(objectPath, true); + this.getConfig().set(objectPath, null); + + // Save the deleted config (may not be the most efficient, but I will handle it later) + if(doSave){ + return saveToDisk(doBackup); + } + + return true; + } + + @Override + public boolean saveToDisk(boolean doBackup) { + boolean deletedSaveSuccess = saveDeletedList(); + + return super.saveToDisk(doBackup) && deletedSaveSuccess; + } + + /** + * Save list of deleted elements. + * @return true if successful. + */ + public boolean saveDeletedList() { + if(this.deletedListConfig == null) return true; + + try { + 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; + } + + return true; + } + + + } + + // Class for itemGroupsManager config - public static class ItemGroupConfigHolder extends ResourceConfigHolder { - private final static String FILE_NAME = "item_groups"; + public static class ItemGroupConfigHolder extends DeletableResource { + private static final String FILE_NAME = "item_groups"; ItemGroupManager itemGroupsManager; @@ -218,8 +354,8 @@ public abstract class ConfigHolder { } // Class for enchant conflict config - public static class ConflictConfigHolder extends ResourceConfigHolder { - private final static String FILE_NAME = "enchant_conflict"; + public static class ConflictConfigHolder extends DeletableResource { + private static final String FILE_NAME = "enchant_conflict"; EnchantConflictManager conflictManager; @@ -242,9 +378,8 @@ public abstract class ConfigHolder { } // Class for unit repair config - public static class UnitRepairHolder extends ResourceConfigHolder { - private final static String ITEM_GROUP_FILE_NAME = "unit_repair_item"; - + public static class UnitRepairHolder extends DeletableResource { + private static final String ITEM_GROUP_FILE_NAME = "unit_repair_item"; private UnitRepairHolder() { super(ITEM_GROUP_FILE_NAME); @@ -258,8 +393,8 @@ public abstract class ConfigHolder { // Class for custom anvil craft - public static class CustomAnvilCraftHolder extends ResourceConfigHolder { - private final static String CUSTOM_RECIPE_FILE_NAME = "custom_recipes"; + public static class CustomAnvilCraftHolder extends DeletableResource { + private static final String CUSTOM_RECIPE_FILE_NAME = "custom_recipes"; CustomAnvilRecipeManager recipeManager; private CustomAnvilCraftHolder() { diff --git a/src/main/java/xyz/alexcrea/cuanvil/config/WorkPenaltyType.java b/src/main/java/xyz/alexcrea/cuanvil/config/WorkPenaltyType.java new file mode 100644 index 0000000..d374999 --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/config/WorkPenaltyType.java @@ -0,0 +1,66 @@ +package xyz.alexcrea.cuanvil.config; + +import com.google.common.collect.ImmutableMap; +import org.jetbrains.annotations.Nullable; +import xyz.alexcrea.cuanvil.anvil.AnvilUseType; + +import java.util.EnumMap; + +public class WorkPenaltyType { + + public record WorkPenaltyPart( + boolean penaltyIncrease, + boolean penaltyAdditive, + boolean exclusivePenaltyIncrease, + boolean exclusivePenaltyAdditive + ) { + + @Override + public boolean equals(Object obj) { + if(!(obj instanceof WorkPenaltyPart other)) return false; + + return other.penaltyIncrease == this.penaltyIncrease && + other.penaltyAdditive == this.penaltyAdditive && + other.exclusivePenaltyIncrease == this.exclusivePenaltyIncrease && + other.exclusivePenaltyAdditive == this.exclusivePenaltyAdditive; + } + + public WorkPenaltyPart(boolean penaltyIncrease, boolean penaltyAdditive) { + this(penaltyIncrease, penaltyAdditive, false, false); + } + } + + private final EnumMap partMap; + + public WorkPenaltyType(@Nullable EnumMap partMap) { + this.partMap = new EnumMap<>(partMap != null ? partMap : new EnumMap<>(AnvilUseType.class)); + } + + public ImmutableMap getPartMap() { + return ImmutableMap.copyOf(partMap); + } + + public WorkPenaltyPart getPenaltyInfo(AnvilUseType type) { + return partMap.getOrDefault(type, type.getDefaultPenalty()); + } + + public boolean isPenaltyIncreasing(AnvilUseType type) { + return partMap.getOrDefault(type, type.getDefaultPenalty()).penaltyIncrease; + } + + public boolean isPenaltyAdditive(AnvilUseType type) { + return partMap.getOrDefault(type, type.getDefaultPenalty()).penaltyAdditive; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof WorkPenaltyType that)) return false; + + for (AnvilUseType type : AnvilUseType.getEntries()) { + if(!getPenaltyInfo(type).equals(that.getPenaltyInfo(type))) return false; + } + return true; + } + +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/AdditionalTestEnchantment.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/AdditionalTestEnchantment.java new file mode 100644 index 0000000..821838f --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/AdditionalTestEnchantment.java @@ -0,0 +1,34 @@ +package xyz.alexcrea.cuanvil.enchant; + +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; + +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 itemType Material namespaced key of the tested item. + * @return If there is a conflict with the enchantments. + */ + boolean isEnchantConflict( + @NotNull Map enchantments, + @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 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 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 new file mode 100644 index 0000000..ea657ac --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/CAEnchantment.java @@ -0,0 +1,249 @@ +package xyz.alexcrea.cuanvil.enchant; + +import org.bukkit.NamespacedKey; +import org.bukkit.entity.HumanEntity; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import xyz.alexcrea.cuanvil.enchant.bulk.BulkCleanEnchantOperation; +import xyz.alexcrea.cuanvil.enchant.bulk.BulkGetEnchantOperation; +import xyz.alexcrea.cuanvil.group.EnchantConflictGroup; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Represent an enchantment compatible with Custom Anvil. + * One issue with custom anvil is: it does not handle well duplicate key name (ignoring namespace) + * as the plugin was initially coded with vanilla enchantment in head + */ +@SuppressWarnings("unused") +public interface CAEnchantment { + + + /** + * Get the default rarity of this enchant. + * @return The default rarity of this enchant. + */ + @NotNull + EnchantmentRarity defaultRarity(); + + /** + * Get the enchantment key. + * @return The enchantment key. + */ + @NotNull + NamespacedKey getKey(); + + /** + * Get the enchantment name. + * @return The enchantment name. + */ + @NotNull + String getName(); + + /** + * Get the default maximum level of this enchantment. + * @return The default maximum level of this enchantment. + */ + int defaultMaxLevel(); + + /** + * Check if the enchantment have specialised get bulk operation. + * @return If the enchantment is optimised for get bulk operation. + */ + boolean isGetOptimised(); + + /** + * Check if the enchantment have specialised clean bulk operation. + * @return If the enchantment is optimised for clean bulk operation. + */ + boolean isCleanOptimised(); + + /** + * Check if the player is allowed to use this enchantment. + * @param player The player to test. + * @return If the player is allowed to use this enchantment. + */ + boolean isAllowed(@NotNull HumanEntity player); + + /** + * Add a conflict to this enchantment conflict list. + * @param conflict The conflict to add. + */ + void addConflict(@NotNull EnchantConflictGroup conflict); + + /** + * Remove a conflict from the conflict list of this enchantment. + * @param conflict The conflict to remove from this enchantment. + */ + void removeConflict(@NotNull EnchantConflictGroup conflict); + + /** + * Clear Custom Anvil conflicts for this enchantment. + */ + void clearConflict(); + + /** + * Get a collection of Custom Anvil conflict containing this enchantment. + * @return A collection of Custom Anvil conflict containing this enchantment. + */ + @NotNull Collection getConflicts(); + + /** + * Get current level of the enchantment. + * @param item Item to search the level for. Should not get changed. + * @return Current leve of this enchantment on item. or 0 if absent. + */ + default int getLevel(@NotNull ItemStack item){ + ItemMeta meta = item.getItemMeta(); + if(meta == null) return 0; + + return getLevel(item, meta); + } + + /** + * Get current level of the enchantment. + * @param item Item to search the level for. Should not get changed. + * @param meta Meta of the provided item. Should not get changed. + * @return Current leve of this enchantment on item. or 0 if absent. + */ + int getLevel(@NotNull ItemStack item, @NotNull ItemMeta meta); + + /** + * Check if this enchantment is present on the provided level. + * @param item The item to set the enchantment level. + * @return If the enchantment have been found. + */ + boolean isEnchantmentPresent(@NotNull ItemStack item); + + /** + * Check if this enchantment is present on the provided level. + * @param item The item to set the enchantment level. + * @param meta Meta of the provided item. It will not be changed and not be set on the item. + * @return If the enchantment have been found. + */ + boolean isEnchantmentPresent(@NotNull ItemStack item, @NotNull ItemMeta meta); + + /** + * Force add an enchantment at the provided level. + * @param item The item to set the enchantment level. + * @param level The level to set the enchantment to. + */ + void addEnchantmentUnsafe(@NotNull ItemStack item, int level); + + /** + * Remove this enchantment from the provided ItemStack. + * @param item The item to remove the enchantment. + */ + void removeFrom(@NotNull ItemStack item); + + // Static functions + /** + * Clear every enchantment from this item. + * @param item Item to be cleared from enchantments. + */ + static void clearEnchants(@NotNull ItemStack item){ + // Optimised enchantment clean using item stack + for (BulkCleanEnchantOperation cleanOperator : CAEnchantmentRegistry.getInstance().getOptimisedCleanOperators()) { + cleanOperator.bulkClear(item); + } + + ItemMeta meta = item.getItemMeta(); + if(meta == null) return; + + // Optimised enchantment clean using item meta + for (BulkCleanEnchantOperation cleanOperator : CAEnchantmentRegistry.getInstance().getOptimisedCleanOperators()) { + cleanOperator.bulkClear(item, meta); + } + + item.setItemMeta(meta); + + // Clean unoptimised enchants + for (CAEnchantment enchant : CAEnchantmentRegistry.getInstance().unoptimisedCleanValues()) { + if(enchant.isEnchantmentPresent(item)){ + enchant.removeFrom(item); + } + } + + } + + /** + * Get enchantments of an item. + * @param item Item to get enchantment from. + * @return A map of the set enchantments and there's respective levels. + */ + static Map getEnchants(@NotNull ItemStack item){ + Map enchantments = new HashMap<>(); + CAEnchantmentRegistry registry = CAEnchantmentRegistry.getInstance(); + + ItemMeta meta = item.getItemMeta(); + if(meta == null) return enchantments; + + // Optimised enchantment get + for (BulkGetEnchantOperation getOperator : CAEnchantmentRegistry.getInstance().getOptimisedGetOperators()) { + getOperator.bulkGet(enchantments, item, meta); + } + + // Unoptimised enchantment get + findEnchantsFromSelectedList(item, meta, enchantments, registry.unoptimisedGetValues()); + + return enchantments; + } + + + /** + * Find enchantments of an item. only test the enchantment from the list. + * @param item Item to get enchantment from. + * @param meta Meta of the provided item. + * @param enchantments Map of enchantment to complete. + * @param enchantmentToTest Enchantment to test + */ + static void findEnchantsFromSelectedList( + @NotNull ItemStack item, + @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)); + } + } + + } + + /** + * Gets an array of all the registered enchantments. + * + * @param key The enchantment key + * @return Array of enchantment. + */ + static @Nullable CAEnchantment getByKey(@NotNull NamespacedKey key){ + return CAEnchantmentRegistry.getInstance().getByKey(key); + } + + /** + * Gets the enchantment by the provided name. + * @param name Name to fetch. + * @return Registered enchantment. null if absent. + * + * @deprecated use {@link #getListByName(String)} + */ + @Deprecated(since = "1.6.3") + static @Nullable CAEnchantment getByName(@NotNull String name){ + return CAEnchantmentRegistry.getInstance().getByName(name); + } + + /** + * Gets list of enchantment using the provided name. + * @param name Name to fetch. + * @return List of registered enchantment. + */ + static List getListByName(@NotNull String name){ + return CAEnchantmentRegistry.getInstance().getListByName(name); + } + +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/CAEnchantmentBase.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/CAEnchantmentBase.java new file mode 100644 index 0000000..05718d5 --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/CAEnchantmentBase.java @@ -0,0 +1,115 @@ +package xyz.alexcrea.cuanvil.enchant; + +import org.bukkit.NamespacedKey; +import org.bukkit.entity.HumanEntity; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import xyz.alexcrea.cuanvil.group.EnchantConflictGroup; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Default implementation of an enchantment compatible with Custom Anvil. + * One issue with custom anvil is: it does not handle well duplicate key name (ignoring namespace) + * as the plugin was initially coded with vanilla enchantment in head + */ +public abstract class CAEnchantmentBase implements CAEnchantment { + + @NotNull + private final NamespacedKey key; + @NotNull + private final String name; + @NotNull + private final EnchantmentRarity defaultRarity; + private final int defaultMaxLevel; + + private final List conflicts; + + /** + * Constructor of Wrapped Enchantment. + * @param key The enchantment's key. + * @param defaultRarity Default rarity the enchantment should be. + * @param defaultMaxLevel Default max level the enchantment can be applied with. + */ + protected CAEnchantmentBase( + @NotNull NamespacedKey key, + @Nullable EnchantmentRarity defaultRarity, + int defaultMaxLevel){ + this.key = key; + this.name = key.getKey(); + this.defaultMaxLevel = defaultMaxLevel; + + this.defaultRarity = Objects.requireNonNullElse(defaultRarity, EnchantmentRarity.COMMON); + + this.conflicts = new ArrayList<>(); + } + + @NotNull + @Override + public final EnchantmentRarity defaultRarity(){ + return defaultRarity; + } + + @NotNull + @Override + public final NamespacedKey getKey(){ + return key; + } + + @NotNull + @Override + public final String getName(){ + return name; + } + + @Override + public final int defaultMaxLevel(){ + return defaultMaxLevel; + } + + @Override + public boolean isGetOptimised(){ + return false; + } + + @Override + public boolean isCleanOptimised(){ + return false; + } + + @Override + public boolean isAllowed(@NotNull HumanEntity player){ + return true; + } + + public boolean isEnchantmentPresent(@NotNull ItemStack item){ + ItemMeta meta = item.getItemMeta(); + if(meta == null) return false; + return isEnchantmentPresent(item, meta); + } + + @Override + public void addConflict(@NotNull EnchantConflictGroup conflict){ + this.conflicts.add(conflict); + } + + @Override + public void removeConflict(@NotNull EnchantConflictGroup conflict){ + this.conflicts.remove(conflict); + } + + @Override + public void clearConflict(){ + this.conflicts.clear(); + } + + @Override + public @NotNull List getConflicts() { + return conflicts; + } + +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/CAEnchantmentRegistry.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/CAEnchantmentRegistry.java new file mode 100644 index 0000000..854ed55 --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/CAEnchantmentRegistry.java @@ -0,0 +1,250 @@ +package xyz.alexcrea.cuanvil.enchant; + +import io.delilaheve.CustomAnvil; +import org.bukkit.NamespacedKey; +import org.bukkit.enchantments.Enchantment; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import xyz.alexcrea.cuanvil.config.ConfigHolder; +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; + +public class CAEnchantmentRegistry { + + private static final CAEnchantmentRegistry instance = new CAEnchantmentRegistry(); + + public static CAEnchantmentRegistry getInstance() { + return instance; + } + + // Register enchantment functions + private final HashMap byKeyMap; + private final HashMap> byNameMap; + + private final SortedSet nameSortedEnchantments; + + private final List unoptimisedGetValues; + private final List unoptimisedCleanValues; + + private final List optimisedGetOperators; + private final List optimisedCleanOperators; + + private CAEnchantmentRegistry() { + byKeyMap = new HashMap<>(); + byNameMap = new HashMap<>(); + + nameSortedEnchantments = new TreeSet<>(Comparator.comparing(CAEnchantment::getName)); + + unoptimisedGetValues = new ArrayList<>(); + unoptimisedCleanValues = new ArrayList<>(); + + optimisedGetOperators = new ArrayList<>(); + optimisedCleanOperators = new ArrayList<>(); + } + + /** + * This should only be called on main of custom anvil. + * If called more than one time, chance of thing being broken will be high. + */ + public void registerBukkit() { + // Register enchantment + for (Enchantment enchantment : Enchantment.values()) { + register(new CABukkitEnchantment(enchantment)); + } + + // Add bukkit enchantment bulk operation + BukkitEnchantBulkOperation bukkitOperation = new BukkitEnchantBulkOperation(); + optimisedGetOperators.add(bukkitOperation); + optimisedCleanOperators.add(bukkitOperation); + } + + private static boolean hasWarnedRegistering = false; + + /** + * Can be used to register new enchantment. + *

    + * No guarantee that the enchantment will be present on the config gui if registered late. + * (By late I mean after custom anvil startup.) + * + * @param enchantment The enchantment to be registered. + * @return If the operation was successful. + */ + public boolean register(@NotNull CAEnchantment enchantment) { + if (byKeyMap.containsKey(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)) { + 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", + error); + MetricsUtil.INSTANCE.trackError(error); + return false; + } + + if ((!hasWarnedRegistering) && byNameMap.containsKey(enchantment.getName())) { + hasWarnedRegistering = true; + + CustomAnvil.instance.getLogger().log(Level.WARNING, + "Duplicate registered enchantment name. Please check that configuration is using namespace."); + } + + byKeyMap.put(enchantment.getKey(), enchantment); + + byNameMap.putIfAbsent(enchantment.getName(), new ArrayList<>()); + byNameMap.get(enchantment.getName()).add(enchantment); + + nameSortedEnchantments.add(enchantment); + + if (!enchantment.isGetOptimised()) { + unoptimisedGetValues.add(enchantment); + } + if (!enchantment.isCleanOptimised()) { + unoptimisedCleanValues.add(enchantment); + } + + return true; + } + + /** + * Can be used to unregister new enchantment. + * Please be cautious with this function. + * It should probably rarely be used. + *

    + * No guarantee that the enchantment will absent if the config guis if unregistered late. + * (By late I mean after custom anvil startup.) + * + * @param enchantment The enchantment to be unregistered. + * @return If the operation was successful. + */ + + public boolean unregister(@Nullable CAEnchantment enchantment) { + if (enchantment == null) return false; + byKeyMap.remove(enchantment.getKey()); + byNameMap.get(enchantment.getName()).remove(enchantment); + + nameSortedEnchantments.remove(enchantment); + + unoptimisedGetValues.remove(enchantment); + unoptimisedCleanValues.remove(enchantment); + return true; + } + + /** + * Gets the enchantment by the provided key. + * + * @param key Key to fetch. + * @return Registered enchantment. null if absent. + */ + @Nullable + public CAEnchantment getByKey(@NotNull NamespacedKey key) { + return byKeyMap.get(key); + } + + /** + * Gets the enchantment by the provided name. + * + * @param name Name to fetch. + * @return Registered enchantment. null if absent. + * @deprecated use {@link #getListByName(String)} + */ + @Deprecated(since = "1.6.3") + @Nullable + public CAEnchantment getByName(@NotNull String name) { + List enchantments = getListByName(name); + if (enchantments.isEmpty()) return null; + + return enchantments.get(0); + } + + /** + * Gets list of enchantment using the provided name. + * + * @param name Name to fetch. + * @return List of registered enchantment. + */ + @NotNull + public List getListByName(@NotNull String name) { + return byNameMap.getOrDefault(name, Collections.emptyList()); + } + + /** + * Gets an array of all the registered enchantments. + * + * @return Array of enchantments. + */ + @NotNull + public Collection values() { + return byKeyMap.values(); + } + + /** + * Gets a map of all the registered enchantments. + * + * @return Immutable map of enchantments. + */ + public Map registeredEnchantments() { + return Collections.unmodifiableMap(byKeyMap); + } + + /** + * Gets a list of all the unoptimised get operation enchantments. + * + * @return List of unoptimised enchantments. + */ + @NotNull + public List unoptimisedGetValues() { + return unoptimisedGetValues; + } + + /** + * Gets a list of all the unoptimised clean operation enchantments. + * + * @return List of unoptimised enchantments. + */ + @NotNull + public List unoptimisedCleanValues() { + return unoptimisedCleanValues; + } + + /** + * Get "clean optimised operation" for get enchantments. + * + * @return Mutable "clean enchantments optimised operation" list. + */ + public List getOptimisedCleanOperators() { + return optimisedCleanOperators; + } + + /** + * Get "get optimised operation" for get enchantments. + * + * @return Mutable "get enchantments optimised operation" list. + */ + public List getOptimisedGetOperators() { + return optimisedGetOperators; + } + + /** + * Get custom anvil enchantment sorted by name. + * + * @return An immutable sorted set of every registered enchantment sorted by name. + */ + public SortedSet getNameSortedEnchantments() { + return Collections.unmodifiableSortedSet(nameSortedEnchantments); + } +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/EnchantmentRarity.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/EnchantmentRarity.java index 50fdfdf..3718f39 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/enchant/EnchantmentRarity.java +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/EnchantmentRarity.java @@ -1,32 +1,52 @@ package xyz.alexcrea.cuanvil.enchant; -// because spigot (1.18) do not support enchantment rarity, I need to do it myself... -public enum EnchantmentRarity { +// because spigot (1.18) do not look like to provide access to enchantment rarity I need to do it myself... +public class EnchantmentRarity { - NO_RARITY(0, 0), - COMMON(1), - UNCOMMON(2), - RARE(4), - VERY_RARE(8); + public static final EnchantmentRarity NO_RARITY = new EnchantmentRarity(0, 0); + public static final EnchantmentRarity COMMON = new EnchantmentRarity(1); + public static final EnchantmentRarity UNCOMMON = new EnchantmentRarity(2); + public static final EnchantmentRarity RARE = new EnchantmentRarity(4); + public static final EnchantmentRarity VERY_RARE = new EnchantmentRarity(8); private final int itemValue; private final int bookValue; - EnchantmentRarity(int itemValue, int bookValue) { + private EnchantmentRarity(int itemValue, int bookValue) { this.itemValue = itemValue; this.bookValue = bookValue; } - EnchantmentRarity(int itemValue) { + private EnchantmentRarity(int itemValue) { this(itemValue, Math.max(1, itemValue / 2)); } - public int getBookValue() { + public final int getBookValue() { return bookValue; } - public int getItemValue() { + public final int getItemValue() { return itemValue; } + + public static EnchantmentRarity getRarity(int itemValue, int bookValue){ + int expectedBook = Math.max(1, itemValue / 2); + if((expectedBook == bookValue) && (itemValue != 0)) return getRarity(itemValue); + + if(itemValue == 0 && bookValue == 0) return NO_RARITY; + return new EnchantmentRarity(itemValue, bookValue); + } + + public static EnchantmentRarity getRarity(int itemValue){ + return switch (itemValue) { + case 0 -> NO_RARITY; + case 1 -> COMMON; + case 2 -> UNCOMMON; + case 4 -> RARE; + case 8 -> VERY_RARE; + default -> new EnchantmentRarity(itemValue); + }; + } + } diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/WrappedEnchantment.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/WrappedEnchantment.java deleted file mode 100644 index 55c8b01..0000000 --- a/src/main/java/xyz/alexcrea/cuanvil/enchant/WrappedEnchantment.java +++ /dev/null @@ -1,321 +0,0 @@ -package xyz.alexcrea.cuanvil.enchant; - -import io.delilaheve.CustomAnvil; -import io.delilaheve.util.ItemUtil; -import org.bukkit.NamespacedKey; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.EnchantmentStorageMeta; -import org.bukkit.inventory.meta.ItemMeta; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import xyz.alexcrea.cuanvil.dependency.DependencyManager; -import xyz.alexcrea.cuanvil.enchant.wrapped.VanillaEnchantment; - -import java.util.*; -import java.util.logging.Level; - -/** - * Represent any enchantment. - * One issue with the plugin is: it does not handle well duplicate key name (ignoring namespace) as the plugin was coded with vanilla enchantment in head - */ -public abstract class WrappedEnchantment { - - @NotNull - private final NamespacedKey key; - @NotNull - private final String name; - @NotNull - private final EnchantmentRarity defaultRarity; - private final int defaultMaxLevel; - - /** - * Constructor of Wrapped Enchantment. - * @param key The enchantment's key. - * @param defaultRarity Default rarity the enchantment should be. - * @param defaultMaxLevel Default max level the enchantment can be applied with. - */ - public WrappedEnchantment( - @NotNull NamespacedKey key, - @Nullable EnchantmentRarity defaultRarity, - int defaultMaxLevel){ - this.key = key; - this.name = key.getKey(); - this.defaultMaxLevel = defaultMaxLevel; - - if(defaultRarity == null) this.defaultRarity = EnchantmentRarity.COMMON; - else this.defaultRarity = defaultRarity; - } - - /** - * Get the default rarity of this enchant. - * @return The default rarity of this enchant. - */ - public final EnchantmentRarity defaultRarity(){ - return defaultRarity; - } - - /** - * Get the enchantment key. - * @return The enchantment key. - */ - @NotNull - public final NamespacedKey getKey(){ - return key; - } - - /** - * Get the enchantment name. - * @return The enchantment name. - */ - @NotNull - public final String getName(){ - return name; - } - - /** - * Get the default maximum level of this enchantment. - * @return The default maximum level of this enchantment. - */ - public final int defaultMaxLevel(){return defaultMaxLevel;} - - /** - * If the enchantment have specialised group operation. - * @return If the enchantment is optimised for group operation. - */ - protected boolean isOptimised(){ - return false; - } - - /** - * Get current level of the enchantment. - * @param item Item to search the level for. - */ - public int getLevel(@NotNull ItemStack item){ - ItemMeta meta = item.getItemMeta(); - if(meta == null) return 0; - return getLevel(item, meta); - } - - /** - * Get current level of the enchantment. - * @param item Item to search the level for. - * @param meta Meta of the provided item. It will not be changed and not be set on the item. - * @return Current leve of this enchantment on item. or 0 if absent. - */ - public abstract int getLevel(@NotNull ItemStack item, @NotNull ItemMeta meta); - - /** - * Check if this enchantment is present on the provided level. - * @param item The item to set the enchantment level. - * @return If the enchantment have been found. - */ - public boolean isEnchantmentPresent(@NotNull ItemStack item){ - ItemMeta meta = item.getItemMeta(); - if(meta == null) return false; - return isEnchantmentPresent(item, meta); - } - - /** - * Check if this enchantment is present on the provided level. - * @param item The item to set the enchantment level. - * @param meta Meta of the provided item. It will not be changed and not be set on the item. - * @return If the enchantment have been found. - */ - public abstract boolean isEnchantmentPresent(@NotNull ItemStack item, @NotNull ItemMeta meta); - - /** - * Force add an enchantment at the provided level. - * @param item The item to set the enchantment level. - * @param level The level to set the enchantment to. - */ - public abstract void addEnchantmentUnsafe(@NotNull ItemStack item, int level); - - /** - * Remove this enchantment from the provided ItemStack. - * @param item The item to remove the enchantment. - */ - public abstract void removeFrom(@NotNull ItemStack item); - - // Static functions - - /** - * Clear every enchantment from this item. - * @param item Item to be cleared from enchantments. - */ - public static void clearEnchants(@NotNull ItemStack item){ - ItemMeta meta = item.getItemMeta(); - if(meta == null) return; - - // Clean Vanilla enchants - if (ItemUtil.INSTANCE.isEnchantedBook(item)) { - EnchantmentStorageMeta bookMeta = (EnchantmentStorageMeta) meta; - bookMeta.getStoredEnchants().forEach( - (enchantment, leve) -> bookMeta.removeStoredEnchant(enchantment) - ); - } else { - item.getEnchantments().forEach( - (enchantment, leve) -> item.removeEnchantment(enchantment) - ); - } - - // Clean unoptimised enchants - for (WrappedEnchantment enchant : unoptimisedValues()) { - if(enchant.isEnchantmentPresent(item)){ - enchant.removeFrom(item); - } - } - - } - - /** - * Get enchantments of an item. - * @param item Item to get enchantment from. - * @return A map of the set enchantments and there's respective levels. - */ - public static Map getEnchants(@NotNull ItemStack item){ - Map enchantments = new HashMap<>(); - - ItemMeta meta = item.getItemMeta(); - if(meta == null) return enchantments; - - // Vanilla optimised get - if (ItemUtil.INSTANCE.isEnchantedBook(item)) { - ((EnchantmentStorageMeta)meta).getStoredEnchants().forEach( - (enchantment, level) -> enchantments.put(getByKey(enchantment.getKey()), level) - ); - } else { - item.getEnchantments().forEach( - (enchantment, level) -> enchantments.put(getByKey(enchantment.getKey()), level) - ); - } - - // Unoptimised enchantment get - findEnchantsFromSelectedList(item, meta, enchantments, unoptimisedValues()); - - return enchantments; - } - - - /** - * Find enchantments of an item. only test the enchantment from the list. - * @param item Item to get enchantment from. - * @param meta Meta of the provided item. - * @param enchantments Map of enchantment to complete. - * @param enchantmentToTest Enchantment to test - */ - private static void findEnchantsFromSelectedList( - @NotNull ItemStack item, - @NotNull ItemMeta meta, - @NotNull Map enchantments, - @NotNull Collection enchantmentToTest){ - - for (WrappedEnchantment enchantment : enchantmentToTest) { - if(enchantment.isEnchantmentPresent(item, meta)){ - enchantments.put(enchantment, enchantment.getLevel(item, meta)); - } - } - - } - - - // Register enchantment functions - private static final HashMap BY_KEY = new HashMap<>(); - private static final HashMap BY_NAME = new HashMap<>(); - private static final List UNOPTIMISED_ENCHANTMENT = new ArrayList<>(); - - /** - * This should only be called on main of custom anvil. - * If called more than one time, chance of thing being broken will be high. - */ - public static void registerEnchantments(){ - for (Enchantment enchantment : Enchantment.values()) { - register(new VanillaEnchantment(enchantment)); - } - - if(DependencyManager.INSTANCE.getEnchantmentSquaredCompatibility() != null){ - DependencyManager.INSTANCE.getEnchantmentSquaredCompatibility().registerEnchantements(); - } - - } - - /** - * Can be used to register new enchantment. - *

    - * No guarantee that the enchantment will be present on the config gui if registered late. - * (By late I mean after custom anvil startup.) - * @param enchantment The enchantment to be registered. - */ - public static void register(@NotNull WrappedEnchantment enchantment){ - if(BY_KEY.containsKey(enchantment.getKey())){ - CustomAnvil.instance.getLogger().log(Level.WARNING, - "Duplicate registered enchantment. This should NOT happen.", - new IllegalStateException(enchantment.getKey()+" enchantment was already registered")); - return; - } - if(BY_NAME.containsKey(enchantment.getName())){ - CustomAnvil.instance.getLogger().log(Level.WARNING, - "Duplicate registered enchantment name. There will have issue. " + - "\nI hope this do not happen to you on a production server. If it do, there is probably a plugin trying to register an enchantment with the same name than another one", - new IllegalStateException(enchantment.getKey()+" enchantment name was already registered")); - } - - BY_KEY.put(enchantment.getKey(), enchantment); - BY_NAME.put(enchantment.getName(), enchantment); - - if(!enchantment.isOptimised()){ - UNOPTIMISED_ENCHANTMENT.add(enchantment); - } - } - - /** - * Can be used to unregister new enchantment. - * Please be cautious with this function. - * It should probably rarely be used. - *

    - * No guarantee that the enchantment will absent if the config guis if unregistered late. - * (By late I mean after custom anvil startup.) - * @param enchantment The enchantment to be unregistered. - */ - public static void unregister(@NotNull WrappedEnchantment enchantment){ - BY_KEY.remove(enchantment.getKey()); - BY_NAME.remove(enchantment.getName()); - } - - /** - * Gets the enchantment by the provided key. - * @param key Key to fetch. - * @return Registered enchantment. null if absent. - */ - public static @Nullable WrappedEnchantment getByKey(@NotNull NamespacedKey key){ - return BY_KEY.get(key); - } - - /** - * Gets the enchantment by the provided name. - * @param name Name to fetch. - * @return Registered enchantment. null if absent. - */ - public static @Nullable WrappedEnchantment getByName(@NotNull String name){ - return BY_NAME.get(name); - } - - /** - * Gets an array of all the registered enchantments. - * @return Array of enchantment. - */ - @NotNull - public static WrappedEnchantment[] values() { - return BY_KEY.values().toArray(new WrappedEnchantment[0]); - } - - /** - * Gets a list of all the unoptimised enchantments. - * @return List of enchantment. - */ - @NotNull - private static List unoptimisedValues() { - return UNOPTIMISED_ENCHANTMENT; - } - -} diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/bulk/BukkitEnchantBulkOperation.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/bulk/BukkitEnchantBulkOperation.java new file mode 100644 index 0000000..73e4185 --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/bulk/BukkitEnchantBulkOperation.java @@ -0,0 +1,66 @@ +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; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.EnchantmentStorageMeta; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.NotNull; +import xyz.alexcrea.cuanvil.api.EnchantmentApi; +import xyz.alexcrea.cuanvil.enchant.CAEnchantment; + +import java.util.Map; + +public class BukkitEnchantBulkOperation implements BulkGetEnchantOperation, BulkCleanEnchantOperation { + + @Override + public void bulkGet(@NotNull Map enchantmentMap, @NotNull ItemStack item, @NotNull ItemMeta meta) { + boolean isBook = ItemUtil.INSTANCE.isEnchantedBook(item); + + if (isBook) { + ((EnchantmentStorageMeta) meta).getStoredEnchants().forEach((enchantment, level) -> + addEnchantment(enchantmentMap, enchantment, level) + ); + } + if(!isBook || ConfigOptions.INSTANCE.getAddBookEnchantmentAsStoredEnchantment()){ + item.getEnchantments().forEach((enchantment, level) -> + addEnchantment(enchantmentMap, enchantment, level) + ); + } + } + + public void addEnchantment(@NotNull Map enchantmentMap, @NotNull Enchantment enchantment, int level) { + CAEnchantment enchant = EnchantmentApi.getByKey(enchantment.getKey()); + if (enchant == null) { + CustomAnvil.instance.getLogger().warning("Enchantment of key " + enchantment.getKey() + + " somehow not found in CustomAnvil ?"); + return; + } + + enchantmentMap.put(enchant, level); + } + + @Override + public void bulkClear(@NotNull ItemStack item) { + if (item.getType() != Material.ENCHANTED_BOOK || ConfigOptions.INSTANCE.getAddBookEnchantmentAsStoredEnchantment()) { + + item.getEnchantments().forEach((enchantment, level) -> + item.removeEnchantment(enchantment) + ); + } + } + + @Override + public void bulkClear(@NotNull ItemStack item, @NotNull ItemMeta meta) { + if (item.getType() == Material.ENCHANTED_BOOK) { + EnchantmentStorageMeta bookMeta = (EnchantmentStorageMeta) meta; + bookMeta.getStoredEnchants().forEach((enchantment, leve) -> + bookMeta.removeStoredEnchant(enchantment) + ); + } + + } +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/bulk/BulkCleanEnchantOperation.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/bulk/BulkCleanEnchantOperation.java new file mode 100644 index 0000000..4b1a225 --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/bulk/BulkCleanEnchantOperation.java @@ -0,0 +1,28 @@ +package xyz.alexcrea.cuanvil.enchant.bulk; + +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.NotNull; + +/** + * Bulk operation for clean enchantments operations. + */ +public interface BulkCleanEnchantOperation { + + /** + * Bulk clear part of the enchantments from this item. + * The item can be edited freely. If you need the meta it is preferred to use {@link #bulkClear(ItemStack, ItemMeta)} if possible + * @param item The item to clear enchantment from. + */ + void bulkClear(@NotNull ItemStack item); + + /** + * Bulk clear part of the enchantments from this item meta. + * Item should not be edited as meta will be applied later. + * If you need to edit the item and do not need the meta use {@link #bulkClear(ItemStack)} + * @param item The item source of the item meta. should not be edited. + * @param meta The item meta to clear enchantment from. + */ + void bulkClear(@NotNull ItemStack item, @NotNull ItemMeta meta); + +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/bulk/BulkGetEnchantOperation.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/bulk/BulkGetEnchantOperation.java new file mode 100644 index 0000000..7c66e8a --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/bulk/BulkGetEnchantOperation.java @@ -0,0 +1,23 @@ +package xyz.alexcrea.cuanvil.enchant.bulk; + +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.NotNull; +import xyz.alexcrea.cuanvil.enchant.CAEnchantment; + +import java.util.Map; + +/** + * Bulk operation for get enchantments operations. + */ +public interface BulkGetEnchantOperation { + + /** + * Bulk get part of the stored enchantment of this item. + * @param enchantmentMap Mutable map of collected enchantment. should b + * @param item The item to get enchantment from. Should not get edited. + * @param meta The item meta to get enchantment from. Should not get edited. + */ + void bulkGet(@NotNull Map enchantmentMap, @NotNull ItemStack item, @NotNull ItemMeta meta); + +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/bulk/EnchantSquaredBulkOperation.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/bulk/EnchantSquaredBulkOperation.java new file mode 100644 index 0000000..57ecf60 --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/bulk/EnchantSquaredBulkOperation.java @@ -0,0 +1,37 @@ +package xyz.alexcrea.cuanvil.enchant.bulk; + +import me.athlaeos.enchantssquared.managers.CustomEnchantManager; +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.plugins.EnchantmentSquaredDependency; +import xyz.alexcrea.cuanvil.enchant.CAEnchantment; + +import java.util.Collections; +import java.util.Map; + +public class EnchantSquaredBulkOperation implements BulkGetEnchantOperation, BulkCleanEnchantOperation { + + @Override + public void bulkGet(@NotNull Map enchantmentMap, @NotNull ItemStack item, @NotNull ItemMeta meta) { + EnchantmentSquaredDependency enchantmentSquared = DependencyManager.INSTANCE.getEnchantmentSquaredCompatibility(); + if(enchantmentSquared != null){ + enchantmentSquared.getEnchantmentsSquared(item, enchantmentMap); + } + } + + + @Override + public void bulkClear(@NotNull ItemStack item) { + EnchantmentSquaredDependency enchantmentSquared = DependencyManager.INSTANCE.getEnchantmentSquaredCompatibility(); + if(enchantmentSquared != null){ + CustomEnchantManager.getInstance().setItemEnchants(item, Collections.emptyMap()); + } + } + + @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/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 new file mode 100644 index 0000000..0e630ea --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CABukkitEnchantment.java @@ -0,0 +1,174 @@ +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; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.EnchantmentStorageMeta; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import xyz.alexcrea.cuanvil.enchant.CAEnchantmentBase; +import xyz.alexcrea.cuanvil.enchant.EnchantmentProperties; +import xyz.alexcrea.cuanvil.enchant.EnchantmentRarity; + +import java.lang.reflect.InvocationTargetException; +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; + +/** + * Custom Anvil enchantment implementation for vanilla registered enchantment. + */ +public class CABukkitEnchantment extends CAEnchantmentBase { + + public final @NotNull Enchantment bukkit; + + public CABukkitEnchantment(@NotNull Enchantment bukkit, @Nullable EnchantmentRarity rarity) { + super(bukkit.getKey(), + rarity, + bukkit.getMaxLevel()); + this.bukkit = bukkit; + } + + public CABukkitEnchantment(@NotNull Enchantment bukkit) { + this(bukkit, getRarity(bukkit)); + } + + @Override + public boolean isGetOptimised() { + return true; + } + + @Override + public boolean isCleanOptimised() { + return true; + } + + @Override + public int getLevel(@NotNull ItemStack item, @NotNull ItemMeta meta) { + if (ItemUtil.INSTANCE.isEnchantedBook(item)) { + return ((EnchantmentStorageMeta) meta).getStoredEnchantLevel(this.bukkit); + } else { + return meta.getEnchantLevel(this.bukkit); + } + } + + @Override + public boolean isEnchantmentPresent(@NotNull ItemStack item, @NotNull ItemMeta meta) { + if (ItemUtil.INSTANCE.isEnchantedBook(item)) { + EnchantmentStorageMeta bookMeta = ((EnchantmentStorageMeta) meta); + + 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()); + + assert bookMeta != null; + bookMeta.addStoredEnchant(this.bukkit, level, true); + item.setItemMeta(bookMeta); + } else { + item.addUnsafeEnchantment(this.bukkit, level); + } + + } + + @Override + public void removeFrom(@NotNull ItemStack item) { + if (ItemUtil.INSTANCE.isEnchantedBook(item)) { + EnchantmentStorageMeta bookMeta = ((EnchantmentStorageMeta) item.getItemMeta()); + + assert bookMeta != null; + bookMeta.removeStoredEnchant(this.bukkit); + bookMeta.removeEnchant(this.bukkit); + item.setItemMeta(bookMeta); + } else { + item.removeEnchantment(this.bukkit); + } + + } + + @NotNull + public static EnchantmentRarity getRarity(Enchantment enchantment) { + try { + return EnchantmentProperties.valueOf(enchantment.getKey().getKey().toUpperCase(Locale.ENGLISH)).getRarity(); + } catch (IllegalArgumentException ignored) { + return findRarity(enchantment); + } + } + + @NotNull + protected Enchantment getEnchant() { + return this.bukkit; + } + + private static Method getAnvilCostMethod; + + static { + Class clazz = Enchantment.class; + try { + getAnvilCostMethod = clazz.getDeclaredMethod("getAnvilCost"); + getAnvilCostMethod.setAccessible(true); + + CustomAnvil.Companion.log("Detected getAnvilCost method"); + } catch (NoSuchMethodException e) { + getAnvilCostMethod = null; + } + + } + + private static final Map targetToGroup = new HashMap<>(); + static { + targetToGroup.put(EnchantmentTarget.ARMOR, "armors"); + targetToGroup.put(EnchantmentTarget.ARMOR_HEAD, "helmets"); + targetToGroup.put(EnchantmentTarget.ARMOR_TORSO, "chestplate"); + targetToGroup.put(EnchantmentTarget.ARMOR_LEGS, "leggings"); + targetToGroup.put(EnchantmentTarget.ARMOR_FEET, "boots"); + targetToGroup.put(EnchantmentTarget.BOW, "bow"); + targetToGroup.put(EnchantmentTarget.BREAKABLE, "can_unbreak"); + targetToGroup.put(EnchantmentTarget.CROSSBOW, "crossbow"); + targetToGroup.put(EnchantmentTarget.FISHING_ROD, "fishing_rod"); + targetToGroup.put(EnchantmentTarget.TOOL, "tools"); + targetToGroup.put(EnchantmentTarget.TRIDENT, "trident"); + targetToGroup.put(EnchantmentTarget.VANISHABLE, "can_vanish"); + targetToGroup.put(EnchantmentTarget.WEAPON, "swords"); + targetToGroup.put(EnchantmentTarget.WEARABLE, "wearable"); + } + + private static EnchantmentRarity findRarity(Enchantment enchantment) { + 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); + + return EnchantmentRarity.COMMON; + } + + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof CABukkitEnchantment other)) { + return false; + } + + return Objects.equals(this.bukkit, other.getEnchant()); + } + +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEPreV5Enchantment.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEPreV5Enchantment.java new file mode 100644 index 0000000..783798d --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEPreV5Enchantment.java @@ -0,0 +1,61 @@ +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 java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.Set; + +public class CAEEPreV5Enchantment extends CABukkitEnchantment implements AdditionalTestEnchantment { + + @NotNull CustomEnchantment eeenchantment; + @NotNull Definition definition; + + public CAEEPreV5Enchantment(@NotNull CustomEnchantment enchantment) { + super(enchantment.getBukkitEnchantment(), getRarity(enchantment.getBukkitEnchantment())); + this.eeenchantment = enchantment; + 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 NamespacedKey itemType) { + if (!definition.hasConflicts()) return false; + + Set conflicts = definition.getConflicts(); + + for (CAEnchantment caEnchantment : enchantments.keySet()) { + if (conflicts.contains(caEnchantment.getName())) 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; + + 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 new file mode 100644 index 0000000..32d1346 --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEcoEnchant.java @@ -0,0 +1,79 @@ +package xyz.alexcrea.cuanvil.enchant.wrapped; + +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; +import xyz.alexcrea.cuanvil.enchant.CAEnchantment; +import xyz.alexcrea.cuanvil.enchant.EnchantmentRarity; + +import java.util.HashMap; +import java.util.Map; + +public class CAEcoEnchant extends CABukkitEnchantment implements AdditionalTestEnchantment { + + private final @NotNull EcoEnchant ecoEnchant; + + public CAEcoEnchant(@NotNull EcoEnchant enchant) { + super(enchant.getEnchantment(), EnchantmentRarity.COMMON); + this.ecoEnchant = enchant; + } + + @Override + 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; + } + + HashMap typeAmountMap = new HashMap<>(); + + for (CAEnchantment other : enchantments.keySet()) { + if (other instanceof CABukkitEnchantment otherVanilla + && this.ecoEnchant.conflictsWith(otherVanilla.getEnchant())) { + return true; + } + + if (other instanceof CAEcoEnchant ecoOther) { + EnchantmentType type = ecoOther.ecoEnchant.getType(); + typeAmountMap.putIfAbsent(type, 0); + + int amount = typeAmountMap.get(type) + 1; + if (amount > type.getLimit()) { + return true; + } + + typeAmountMap.put(type, amount); + } + + } + + return false; + } + + @Override + public boolean isItemConflict(@NotNull Map enchantments, + @NotNull NamespacedKey itemType, + @NotNull ItemStack item) { + if (Material.ENCHANTED_BOOK.getKey().equals(itemType)) { + return false; + } + + for (EnchantmentTarget target : this.ecoEnchant.getTargets()) { + if (target.matches(item)) { + return false; + } + } + + return true; + } +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEnchantSquaredEnchantment.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEnchantSquaredEnchantment.java new file mode 100644 index 0000000..8f1058e --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEnchantSquaredEnchantment.java @@ -0,0 +1,79 @@ +package xyz.alexcrea.cuanvil.enchant.wrapped; + +import me.athlaeos.enchantssquared.enchantments.CustomEnchant; +import me.athlaeos.enchantssquared.managers.CustomEnchantManager; +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.dependency.DependencyManager; +import xyz.alexcrea.cuanvil.enchant.CAEnchantmentBase; +import xyz.alexcrea.cuanvil.enchant.EnchantmentRarity; + +import java.util.Map; +import java.util.Objects; + +public class CAEnchantSquaredEnchantment extends CAEnchantmentBase { + + public final @NotNull CustomEnchant enchant; + + public CAEnchantSquaredEnchantment(@NotNull CustomEnchant enchant) { + super(Objects.requireNonNull( + Objects.requireNonNull(DependencyManager.INSTANCE.getEnchantmentSquaredCompatibility()).getKeyFromEnchant(enchant)), + EnchantmentRarity.COMMON, + enchant.getMaxLevel()); + this.enchant = enchant; + + } + + public @NotNull CustomEnchant getEnchant() { + return enchant; + } + + @Override + public boolean isGetOptimised() { + return true; + } + + @Override + public boolean isCleanOptimised() { + return true; + } + + @Override + public boolean isAllowed(@NotNull HumanEntity human) { + return this.enchant.hasPermission(human); + } + + @Override + public int getLevel(@NotNull ItemStack item, @NotNull ItemMeta meta) { + return CustomEnchantManager.getInstance().getEnchantStrength(item, this.enchant.getType()); + } + + @Override + public boolean isEnchantmentPresent(@NotNull ItemStack item, @NotNull ItemMeta meta) { + Map enchants = CustomEnchantManager.getInstance().getItemsEnchantsFromPDC(item); + return enchants.containsKey(this.enchant); + } + + @Override + public void addEnchantmentUnsafe(@NotNull ItemStack item, int level) { + CustomEnchantManager.getInstance().addEnchant(item, this.enchant.getType(), level); + } + + @Override + public void removeFrom(@NotNull ItemStack item) { + CustomEnchantManager.getInstance().removeEnchant(item, this.enchant.getType()); + } + + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof CAEnchantSquaredEnchantment other)) { + return false; + } + + return this.enchant.equals(other.getEnchant()); + } + +} 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 new file mode 100644 index 0000000..74068d4 --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CALegacyEEEnchantment.java @@ -0,0 +1,44 @@ +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; +import xyz.alexcrea.cuanvil.enchant.AdditionalTestEnchantment; +import xyz.alexcrea.cuanvil.enchant.CAEnchantment; +import xyz.alexcrea.cuanvil.enchant.EnchantmentRarity; + +import java.util.Map; +import java.util.Set; + +public class CALegacyEEEnchantment extends CABukkitEnchantment implements AdditionalTestEnchantment { + + @NotNull EnchantmentData eeenchantment; + + public CALegacyEEEnchantment(@NotNull EnchantmentData enchantment) { + super(enchantment.getEnchantment(), EnchantmentRarity.getRarity(enchantment.getAnvilCost())); + this.eeenchantment = enchantment; + + } + + @Override + public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemType) { + if (!eeenchantment.hasConflicts()) return false; + + Set conflicts = eeenchantment.getConflicts(); + + for (CAEnchantment caEnchantment : enchantments.keySet()) { + if (conflicts.contains(caEnchantment.getName())) 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; + + 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 new file mode 100644 index 0000000..cb24def --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CALegacyEcoEnchant.java @@ -0,0 +1,68 @@ +package xyz.alexcrea.cuanvil.enchant.wrapped; + +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; + +public class CALegacyEcoEnchant extends CABukkitEnchantment implements AdditionalTestEnchantment { + + private final @NotNull EcoEnchant ecoEnchant; + + public CALegacyEcoEnchant(@NotNull EcoEnchant ecoEnchant, @NotNull Enchantment enchantment) { + super(enchantment, EnchantmentRarity.COMMON); + this.ecoEnchant = ecoEnchant; + } + + @Override + public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemType) { + if (enchantments.isEmpty()) return false; + + EnchantmentType type = this.ecoEnchant.getType(); + boolean isSingular = type.isSingular(); + + for (CAEnchantment other : enchantments.keySet()) { + if (other instanceof CABukkitEnchantment otherVanilla + && this.ecoEnchant.conflictsWith(otherVanilla.getEnchant())) { + return true; + } + + if (isSingular && + other != this && + (other instanceof CALegacyEcoEnchant otherEco) && + type.equals(otherEco.ecoEnchant.getType())) { + 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; + } + + var mat = MaterialUtil.INSTANCE.getMatFromKey(itemType); + for (EnchantmentTarget target : this.ecoEnchant.getTargets()) { + if (target.getMaterials().contains(mat)) { + return false; + } + } + + return true; + } +} 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/enchant/wrapped/EnchantSquaredEnchantment.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/EnchantSquaredEnchantment.java deleted file mode 100644 index 3683487..0000000 --- a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/EnchantSquaredEnchantment.java +++ /dev/null @@ -1,47 +0,0 @@ -package xyz.alexcrea.cuanvil.enchant.wrapped; - -import me.athlaeos.enchantssquared.enchantments.CustomEnchant; -import me.athlaeos.enchantssquared.managers.CustomEnchantManager; -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.WrappedEnchantment; - -import java.util.Map; -import java.util.Objects; - -public class EnchantSquaredEnchantment extends WrappedEnchantment { - - private final @NotNull CustomEnchant enchant; - public EnchantSquaredEnchantment(@NotNull CustomEnchant enchant, @NotNull Plugin enchantSquared) { - super(Objects.requireNonNull(NamespacedKey.fromString(enchant.getType().toLowerCase(), enchantSquared)), null, enchant.getMaxLevel()); - this.enchant = enchant; - - } - - //TODO optimise for bulk operation - - @Override - public int getLevel(@NotNull ItemStack item, @NotNull ItemMeta meta) { - return CustomEnchantManager.getInstance().getEnchantStrength(item, this.enchant.getType()); - } - - @Override - public boolean isEnchantmentPresent(@NotNull ItemStack item, @NotNull ItemMeta meta) { - Map enchants = CustomEnchantManager.getInstance().getItemsEnchantsFromPDC(item); - return enchants.containsKey(this.enchant); - } - - @Override - public void addEnchantmentUnsafe(@NotNull ItemStack item, int level) { - CustomEnchantManager.getInstance().addEnchant(item, this.enchant.getType(), level); - } - - @Override - public void removeFrom(@NotNull ItemStack item) { - CustomEnchantManager.getInstance().removeEnchant(item, this.enchant.getType()); - } - -} diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/VanillaEnchantment.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/VanillaEnchantment.java deleted file mode 100644 index 17b0201..0000000 --- a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/VanillaEnchantment.java +++ /dev/null @@ -1,87 +0,0 @@ -package xyz.alexcrea.cuanvil.enchant.wrapped; - -import io.delilaheve.util.ItemUtil; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.EnchantmentStorageMeta; -import org.bukkit.inventory.meta.ItemMeta; -import org.jetbrains.annotations.NotNull; -import xyz.alexcrea.cuanvil.enchant.EnchantmentProperties; -import xyz.alexcrea.cuanvil.enchant.EnchantmentRarity; -import xyz.alexcrea.cuanvil.enchant.WrappedEnchantment; - -import java.util.Locale; - -public class VanillaEnchantment extends WrappedEnchantment { - - private final @NotNull Enchantment enchantment; - - public VanillaEnchantment(@NotNull Enchantment enchantment){ - super(enchantment.getKey(), - getRarity(enchantment), - enchantment.getMaxLevel()); - this.enchantment = enchantment; - - } - - @Override - protected boolean isOptimised() { - return true; - } - - @Override - public int getLevel(@NotNull ItemStack item, @NotNull ItemMeta meta) { - if (ItemUtil.INSTANCE.isEnchantedBook(item)) { - return ((EnchantmentStorageMeta)meta).getStoredEnchantLevel(this.enchantment); - } else { - return meta.getEnchantLevel(this.enchantment); - } - } - - @Override - public boolean isEnchantmentPresent(@NotNull ItemStack item, @NotNull ItemMeta meta) { - if (ItemUtil.INSTANCE.isEnchantedBook(item)) { - EnchantmentStorageMeta bookMeta = ((EnchantmentStorageMeta)meta); - - return bookMeta.getStoredEnchants().containsKey(this.enchantment); - }else{ - return item.containsEnchantment(this.enchantment); - } - } - - @Override - public void addEnchantmentUnsafe(@NotNull ItemStack item, int level) { - if (ItemUtil.INSTANCE.isEnchantedBook(item)) { - EnchantmentStorageMeta bookMeta = ((EnchantmentStorageMeta)item.getItemMeta()); - - assert bookMeta != null; - bookMeta.addStoredEnchant(this.enchantment, level, true); - item.setItemMeta(bookMeta); - } else { - item.addUnsafeEnchantment(this.enchantment, level); - } - - } - - @Override - public void removeFrom(@NotNull ItemStack item) { - if (ItemUtil.INSTANCE.isEnchantedBook(item)) { - EnchantmentStorageMeta bookMeta = ((EnchantmentStorageMeta)item.getItemMeta()); - - assert bookMeta != null; - bookMeta.removeStoredEnchant(this.enchantment); - item.setItemMeta(bookMeta); - }else{ - item.removeEnchantment(this.enchantment); - } - - } - - public static EnchantmentRarity getRarity(Enchantment enchantment){ - try {return EnchantmentProperties.valueOf(enchantment.getKey().getKey().toUpperCase(Locale.ENGLISH)).getRarity();} - catch (IllegalArgumentException ignored) {} - - return EnchantmentRarity.COMMON; - } - -} diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/ValueUpdatableGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/ValueUpdatableGui.java index 3cf65f0..16bc082 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/ValueUpdatableGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/ValueUpdatableGui.java @@ -1,20 +1,11 @@ package xyz.alexcrea.cuanvil.gui; -import com.github.stefvanschie.inventoryframework.adventuresupport.TextHolder; -import com.github.stefvanschie.inventoryframework.gui.type.ChestGui; -import org.bukkit.plugin.Plugin; -import org.jetbrains.annotations.NotNull; +import com.github.stefvanschie.inventoryframework.gui.type.util.Gui; -public abstract class ValueUpdatableGui extends ChestGui { +public interface ValueUpdatableGui { - public ValueUpdatableGui(int rows, @NotNull String title, @NotNull Plugin plugin) { - super(rows, title, plugin); - } + void updateGuiValues(); - public ValueUpdatableGui(int rows, @NotNull TextHolder title, @NotNull Plugin plugin) { - super(rows, title, plugin); - } - - public abstract void updateGuiValues(); + Gui getConnectedGui(); } 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 e41e0e3..cc4fddc 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/MainConfigGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/MainConfigGui.java @@ -8,28 +8,29 @@ import io.delilaheve.CustomAnvil; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; +import xyz.alexcrea.cuanvil.dependency.packet.PacketManager; import xyz.alexcrea.cuanvil.gui.config.global.*; import xyz.alexcrea.cuanvil.gui.util.GuiGlobalItems; -import xyz.alexcrea.cuanvil.dependency.protocolib.PacketManager; +import xyz.alexcrea.cuanvil.gui.util.GuiSharedConstant; import java.util.Collections; public class MainConfigGui extends ChestGui { - private final static MainConfigGui INSTANCE = new MainConfigGui(); + private static final MainConfigGui INSTANCE = new MainConfigGui(); public static MainConfigGui getInstance() { return INSTANCE; } private MainConfigGui() { - super(3, "\u00A78Anvil Config", CustomAnvil.instance); + super(3, "§8Anvil Config", CustomAnvil.instance); } public void init(PacketManager packetManager) { Pattern pattern = new Pattern( - "000000000", - "012304567", + GuiSharedConstant.EMPTY_GUI_FULL_LINE, + "012345678", "Q00000000" ); PatternPane pane = new PatternPane(0, 0, 9, 3, pattern); @@ -42,8 +43,8 @@ public class MainConfigGui extends ChestGui { ItemMeta basicConfigMeta = basicConfigItemstack.getItemMeta(); assert basicConfigMeta != null; - basicConfigMeta.setDisplayName("\u00A7aBasic Config Menu"); - basicConfigMeta.setLore(Collections.singletonList("\u00A77Click here to open basic config menu")); + basicConfigMeta.setDisplayName("§aBasic Config Menu"); + basicConfigMeta.setLore(Collections.singletonList("§7Click here to open basic config menu")); basicConfigItemstack.setItemMeta(basicConfigMeta); GuiItem basicConfigItem = GuiGlobalItems.goToGuiItem(basicConfigItemstack, new BasicConfigGui(packetManager)); @@ -54,80 +55,92 @@ public class MainConfigGui extends ChestGui { ItemMeta enchantLimitMeta = enchantLimitItemstack.getItemMeta(); assert enchantLimitMeta != null; - enchantLimitMeta.setDisplayName("\u00A7aEnchantment Level Limit"); - enchantLimitMeta.setLore(Collections.singletonList("\u00A77Click here to open enchantment level limit menu")); + enchantLimitMeta.setDisplayName("§aEnchantment Level Limit"); + enchantLimitMeta.setLore(Collections.singletonList("§7Click here to open enchantment level limit menu")); enchantLimitItemstack.setItemMeta(enchantLimitMeta); - GuiItem enchantLimitItem = GuiGlobalItems.goToGuiItem(enchantLimitItemstack, EnchantLimitConfigGui.INSTANCE); + GuiItem enchantLimitItem = GuiGlobalItems.goToGuiItem(enchantLimitItemstack, new EnchantLimitConfigGui()); pane.bindItem('2', enchantLimitItem); + // enchant level limit item + ItemStack enchantMergeLimitItemstack = new ItemStack(Material.ENCHANTED_BOOK); + ItemMeta enchantMergeLimitMeta = enchantMergeLimitItemstack.getItemMeta(); + assert enchantMergeLimitMeta != null; + + enchantMergeLimitMeta.setDisplayName("§aEnchantment Merge Limit"); + enchantMergeLimitMeta.setLore(Collections.singletonList("§7Click here to open enchantment merge limit menu")); + enchantMergeLimitItemstack.setItemMeta(enchantMergeLimitMeta); + + GuiItem enchantMergeLimitItem = GuiGlobalItems.goToGuiItem(enchantMergeLimitItemstack, new EnchantMergeLimitConfigGui()); + pane.bindItem('3', enchantMergeLimitItem); + // enchant cost item ItemStack enchantCostItemstack = new ItemStack(Material.EXPERIENCE_BOTTLE); ItemMeta enchantCostMeta = enchantCostItemstack.getItemMeta(); assert enchantCostMeta != null; - enchantCostMeta.setDisplayName("\u00A7aEnchantment Cost"); - enchantCostMeta.setLore(Collections.singletonList("\u00A77Click here to open enchantment costs menu")); + enchantCostMeta.setDisplayName("§aEnchantment Cost"); + enchantCostMeta.setLore(Collections.singletonList("§7Click here to open enchantment costs menu")); enchantCostItemstack.setItemMeta(enchantCostMeta); - GuiItem enchantCostItem = GuiGlobalItems.goToGuiItem(enchantCostItemstack, EnchantCostConfigGui.INSTANCE); - pane.bindItem('3', enchantCostItem); + GuiItem enchantCostItem = GuiGlobalItems.goToGuiItem(enchantCostItemstack, new EnchantCostConfigGui()); + pane.bindItem('4', enchantCostItem); // Enchantment Conflicts item ItemStack enchantConflictItemstack = new ItemStack(Material.OAK_FENCE); ItemMeta enchantConflictMeta = enchantConflictItemstack.getItemMeta(); assert enchantConflictMeta != null; - enchantConflictMeta.setDisplayName("\u00A7aEnchantment Conflict"); - enchantConflictMeta.setLore(Collections.singletonList("\u00A77Click here to open enchantment conflict menu")); + enchantConflictMeta.setDisplayName("§aEnchantment Conflict"); + enchantConflictMeta.setLore(Collections.singletonList("§7Click here to open enchantment conflict menu")); enchantConflictItemstack.setItemMeta(enchantConflictMeta); - GuiItem enchantConflictItem = GuiGlobalItems.goToGuiItem(enchantConflictItemstack, EnchantConflictGui.INSTANCE); - pane.bindItem('4', enchantConflictItem); + GuiItem enchantConflictItem = GuiGlobalItems.goToGuiItem(enchantConflictItemstack, EnchantConflictGui.getInstance()); + pane.bindItem('5', enchantConflictItem); // Group config items ItemStack groupItemstack = new ItemStack(Material.CHEST); ItemMeta groupMeta = groupItemstack.getItemMeta(); assert groupMeta != null; - groupMeta.setDisplayName("\u00A7aGroups"); - groupMeta.setLore(Collections.singletonList("\u00A77Click 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.INSTANCE); + GuiItem groupConfigItem = GuiGlobalItems.goToGuiItem(groupItemstack, GroupConfigGui.getInstance()); - pane.bindItem('5', groupConfigItem); + pane.bindItem('6', groupConfigItem); // Unit repair item ItemStack unirRepairItemstack = new ItemStack(Material.DIAMOND); ItemMeta unitRepairMeta = unirRepairItemstack.getItemMeta(); assert unitRepairMeta != null; - unitRepairMeta.setDisplayName("\u00A7aUnit Repair"); - unitRepairMeta.setLore(Collections.singletonList("\u00A77Click here to open anvil unit repair menu")); + unitRepairMeta.setDisplayName("§aUnit Repair"); + unitRepairMeta.setLore(Collections.singletonList("§7Click here to open anvil unit repair menu")); unirRepairItemstack.setItemMeta(unitRepairMeta); - GuiItem unitRepairItem = GuiGlobalItems.goToGuiItem(unirRepairItemstack, UnitRepairConfigGui.INSTANCE); - pane.bindItem('6', unitRepairItem); + GuiItem unitRepairItem = GuiGlobalItems.goToGuiItem(unirRepairItemstack, UnitRepairConfigGui.getInstance()); + pane.bindItem('7', unitRepairItem); // Custom recipe item ItemStack customRecipeItemstack = new ItemStack(Material.CRAFTING_TABLE); ItemMeta customRecipeMeta = customRecipeItemstack.getItemMeta(); assert customRecipeMeta != null; - customRecipeMeta.setDisplayName("\u00A7aCustom recipes"); - customRecipeMeta.setLore(Collections.singletonList("\u00A77Click here to open anvil custom recipe menu")); + customRecipeMeta.setDisplayName("§aCustom recipes"); + customRecipeMeta.setLore(Collections.singletonList("§7Click here to open anvil custom recipe menu")); customRecipeItemstack.setItemMeta(customRecipeMeta); - GuiItem customRecipeItem = GuiGlobalItems.goToGuiItem(customRecipeItemstack, CustomRecipeConfigGui.INSTANCE); - pane.bindItem('7', customRecipeItem); + GuiItem customRecipeItem = GuiGlobalItems.goToGuiItem(customRecipeItemstack, CustomRecipeConfigGui.getInstance()); + pane.bindItem('8', customRecipeItem); // quit item ItemStack quitItemstack = new ItemStack(Material.BARRIER); ItemMeta quitMeta = quitItemstack.getItemMeta(); assert quitMeta != null; - quitMeta.setDisplayName("\u00A7cQuit"); + quitMeta.setDisplayName("§cQuit"); quitItemstack.setItemMeta(quitMeta); GuiItem quitItem = new GuiItem(quitItemstack, event -> { diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/SelectEnchantmentContainer.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/SelectEnchantmentContainer.java index 95bf586..1174209 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/SelectEnchantmentContainer.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/SelectEnchantmentContainer.java @@ -1,15 +1,15 @@ package xyz.alexcrea.cuanvil.gui.config; -import xyz.alexcrea.cuanvil.enchant.WrappedEnchantment; +import xyz.alexcrea.cuanvil.enchant.CAEnchantment; import java.util.Set; public interface SelectEnchantmentContainer { - Set getSelectedEnchantments(); + Set getSelectedEnchantments(); - boolean setSelectedEnchantments(Set enchantments); + boolean setSelectedEnchantments(Set enchantments); - Set illegalEnchantments(); + Set illegalEnchantments(); } diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/SelectGroupContainer.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/SelectGroupContainer.java index c8668d0..49f8b3b 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/SelectGroupContainer.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/SelectGroupContainer.java @@ -19,12 +19,12 @@ public interface SelectGroupContainer { static List getGroupLore(SelectGroupContainer container, String containerType, String groupAction){ // Prepare group lore ArrayList groupLore = new ArrayList<>(); - groupLore.add("\u00A77Allow you to select a list of \u00A73Groups \u00A77that this " + containerType + " should " + groupAction); + groupLore.add("§7Allow you to select a list of §3Groups §7that this " + containerType + " should " + groupAction); Set grouos = container.getSelectedGroups(); if (grouos.isEmpty()) { - groupLore.add("\u00A77There is no "+groupAction+"d group for this "+containerType+"."); + groupLore.add("§7There is no "+groupAction+"d group for this "+containerType+"."); } else { - groupLore.add("\u00A77List of "+groupAction+"d groups for this "+containerType+":"); + groupLore.add("§7List of "+groupAction+"d groups for this "+containerType+":"); Iterator groupIterator = grouos.iterator(); boolean greaterThanMax = grouos.size() > 5; @@ -32,11 +32,11 @@ public interface SelectGroupContainer { for (int i = 0; i < maxindex; i++) { // format string like "- Melee Weapons" String formattedName = CasedStringUtil.snakeToUpperSpacedCase(groupIterator.next().getName()); - groupLore.add("\u00A77- \u00A73" + formattedName); + groupLore.add("§7- §3" + formattedName); } if (greaterThanMax) { - groupLore.add("\u00A77And " + (grouos.size() - 4) + " more..."); + groupLore.add("§7And " + (grouos.size() - 4) + " more..."); } } return groupLore; 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 397030e..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,39 +1,40 @@ 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("\u00A77Allow you to select a list of \u00A7ematerials \u00A77that this " + containerType + " should " + action); - Set materialSet = container.getSelectedMaterials(); + groupLore.add("§7Allow you to select a list of §ematerials §7that this " + containerType + " should " + action); + Set materialSet = container.getSelectedMaterials(); if (materialSet.isEmpty()) { - groupLore.add("\u00A77There is no "+action+"d material for this "+containerType+"."); + groupLore.add("§7There is no "+action+"d material for this "+containerType+"."); } else { - groupLore.add("\u00A77List of "+action+"d materials for this "+containerType+":"); - Iterator materialIterator = materialSet.iterator(); + groupLore.add("§7List of "+action+"d materials for this "+containerType+":"); + 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()); - groupLore.add("\u00A77- \u00A7e" + formattedName); + String formattedName = CasedStringUtil.snakeToUpperSpacedCase(materialIterator.next().getKey().toLowerCase()); + groupLore.add("§7- §e" + formattedName); } if (greaterThanMax) { - groupLore.add("\u00A77And " + (materialSet.size() - 4) + " more..."); + groupLore.add("§7And " + (materialSet.size() - 4) + " more..."); } } return groupLore; 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 d306ff1..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,11 +42,12 @@ 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; } if (!success) { - event.getWhoClicked().sendMessage("\u00A7cAction could not be completed. "); + event.getWhoClicked().sendMessage("§cAction could not be completed. "); } backOnConfirm.show(player); @@ -55,7 +57,7 @@ public class ConfirmActionGui extends AbstractAskGui { ItemStack infoItem = new ItemStack(Material.PAPER); ItemMeta infoMeta = infoItem.getItemMeta(); - infoMeta.setDisplayName("\u00A7eAre you sure ?"); + infoMeta.setDisplayName("§eAre you sure ?"); if(actionDescription != null){ infoMeta.setLore(Arrays.asList(actionDescription.split("\n"))); } 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 eb15243..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){ @@ -71,7 +72,7 @@ public class SelectItemTypeGui extends AbstractAskGui { this.pane.bindItem('V', selectGuiItem.get()); // Temporary leave item - GuiItem temporaryLeave = GuiGlobalItems.temporaryCloseGuiToSelectItem(Material.YELLOW_TERRACOTTA, this); + GuiItem temporaryLeave = GuiGlobalItems.temporaryCloseGuiToSelectItem(Material.YELLOW_STAINED_GLASS_PANE, this); this.pane.bindItem('s', temporaryLeave); @@ -80,7 +81,7 @@ public class SelectItemTypeGui extends AbstractAskGui { private ItemStack setDisplayMeta(ItemStack item, String actionDescription){ ItemMeta meta = item.getItemMeta(); - meta.setDisplayName("\u00A7ePlace an item here"); + meta.setDisplayName("§ePlace an item here"); meta.setLore(Arrays.asList(actionDescription.split("\n"))); item.setItemMeta(meta); 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 4bbd48a..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 @@ -1,23 +1,26 @@ package xyz.alexcrea.cuanvil.gui.config.global; import com.github.stefvanschie.inventoryframework.gui.GuiItem; -import com.github.stefvanschie.inventoryframework.pane.Orientable; -import com.github.stefvanschie.inventoryframework.pane.OutlinePane; -import io.delilaheve.CustomAnvil; -import xyz.alexcrea.cuanvil.enchant.WrappedEnchantment; +import com.github.stefvanschie.inventoryframework.pane.util.Pattern; +import org.bukkit.event.inventory.InventoryClickEvent; +import xyz.alexcrea.cuanvil.enchant.CAEnchantment; +import xyz.alexcrea.cuanvil.enchant.CAEnchantmentRegistry; import xyz.alexcrea.cuanvil.gui.ValueUpdatableGui; -import xyz.alexcrea.cuanvil.gui.config.settings.AbstractSettingGui; +import xyz.alexcrea.cuanvil.gui.config.list.SettingGuiListConfigGui; +import xyz.alexcrea.cuanvil.gui.config.settings.SettingGui; import xyz.alexcrea.cuanvil.gui.util.GuiSharedConstant; -import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.List; +import java.util.function.Consumer; /** * Abstract Global Config gui for enchantment setting configuration. * * @param Type of the factory of the type of setting the gui should edit. */ -public abstract class AbstractEnchantConfigGui extends ValueUpdatableGui { +public abstract class AbstractEnchantConfigGui extends SettingGuiListConfigGui{ /** * Constructor for a gui displaying available enchantment to edit a enchantment setting. @@ -25,63 +28,90 @@ public abstract class AbstractEnchantConfigGui bookItemFactoryList; - - /** - * Prepare enchantment config gui displayed items factory. - */ - protected void prepareValues() { - bookItemFactoryList = new ArrayList<>(); - - for (WrappedEnchantment enchant : GuiSharedConstant.SORTED_ENCHANTMENT_LIST) { - T factory = getFactoryFromEnchant(enchant); - - bookItemFactoryList.add(factory); - } + super(title); } @Override - public void updateGuiValues() { + public void updateGuiValues() { //TODO maybe optimise it. + reloadValues(); + } - // probably not the most efficient but hey ! it do the work - // TODO optimise one day.. maybe + @Override + protected Collection getEveryDisplayableInstanceOfGeneric() { + return CAEnchantmentRegistry.getInstance().getNameSortedEnchantments(); + } - this.filledEnchant.clear(); + @Override + protected Pattern getBackgroundPattern(){ + return new Pattern( + GuiSharedConstant.UPPER_FILLER_FULL_PLANE, + GuiSharedConstant.EMPTY_FILLER_FULL_LINE, + GuiSharedConstant.EMPTY_FILLER_FULL_LINE, + GuiSharedConstant.EMPTY_FILLER_FULL_LINE, + GuiSharedConstant.EMPTY_FILLER_FULL_LINE, + "B11L1R111" + ); + } - for (T inventoryFactory : this.bookItemFactoryList) { - GuiItem item = getItemFromFactory(inventoryFactory); + @Override + public void updateValueForGeneric(CAEnchantment generic, boolean shouldUpdate) { + updateValueForGeneric(generic, shouldUpdate, true); + } + + public void updateValueForGeneric(CAEnchantment generic, boolean shouldUpdate, boolean prepareSorting) { + if(!prepareSorting) { + super.updateValueForGeneric(generic, shouldUpdate); + return; + } + + if(!this.factoryMap.containsKey(generic)){ + // We need to sort elements again + super.updateValueForGeneric(generic, false); + + // Clear page then refill all of them + this.firstPage.clear(); + this.pages.clear(); + this.pages.add(this.firstPage); + + for (CAEnchantment enchantment : getEveryDisplayableInstanceOfGeneric()) { + GuiItem item = this.guiItemMap.get(enchantment); + + if(item == null) { + updateValueForGeneric(enchantment, false, false); + }else { + addToPage(item); + } + + } + + if(shouldUpdate) update(); + + }else{ + super.updateValueForGeneric(generic, shouldUpdate); - this.filledEnchant.addItem(item); } - update(); } - public abstract T getFactoryFromEnchant(WrappedEnchantment enchant); + // Unused methods + @Override + protected GuiItem prepareCreateNewItem() { + return null; + } - public abstract GuiItem getItemFromFactory(T inventoryFactory); + @Override + protected List getCreateItemLore() { + return Collections.emptyList(); + } + @Override + protected Consumer getCreateClickConsumer() { + return null; + } + + @Override + protected String createItemName() { + return null; + } } 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 3ab84a9..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 @@ -1,6 +1,8 @@ package xyz.alexcrea.cuanvil.gui.config.global; import com.github.stefvanschie.inventoryframework.gui.GuiItem; +import com.github.stefvanschie.inventoryframework.gui.type.ChestGui; +import com.github.stefvanschie.inventoryframework.gui.type.util.Gui; import com.github.stefvanschie.inventoryframework.pane.PatternPane; import com.github.stefvanschie.inventoryframework.pane.util.Pattern; import io.delilaheve.CustomAnvil; @@ -10,15 +12,18 @@ import org.bukkit.Material; import org.bukkit.inventory.ItemStack; 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; import xyz.alexcrea.cuanvil.gui.config.settings.BoolSettingsGui; import xyz.alexcrea.cuanvil.gui.config.settings.IntSettingsGui; +import xyz.alexcrea.cuanvil.gui.config.settings.WorkPenaltyTypeSettingGui; 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.dependency.protocolib.PacketManager; import java.util.ArrayList; import java.util.Arrays; @@ -27,10 +32,11 @@ import java.util.Collections; /** * Global config to edit basic basic settings. */ -public class BasicConfigGui extends ValueUpdatableGui { +public class BasicConfigGui extends ChestGui implements ValueUpdatableGui { - private static BasicConfigGui INSTANCE; + private static BasicConfigGui INSTANCE = null; + @Nullable public static BasicConfigGui getInstance() { return INSTANCE; } @@ -40,8 +46,8 @@ public class BasicConfigGui extends ValueUpdatableGui { * Constructor of this Global gui for basic settings. */ public BasicConfigGui(PacketManager packetManager) { - super(4, "\u00A78Basic Config", CustomAnvil.instance); - INSTANCE = this; + super(4, "§8Basic Config", CustomAnvil.instance); + if(INSTANCE == null) INSTANCE = this; this.packetManager = packetManager; init(); @@ -55,8 +61,8 @@ public class BasicConfigGui extends ValueUpdatableGui { private void init() { Pattern pattern = new Pattern( GuiSharedConstant.EMPTY_GUI_FULL_LINE, - "0L0T0I0S0", - "0C0R0U0r0", + "LT0IWS0cp", + "CR0U0r0hP", "B00000000" ); pane = new PatternPane(0, 0, 9, 4, pattern); @@ -69,9 +75,9 @@ public class BasicConfigGui extends ValueUpdatableGui { updateGuiValues(); } - private BoolSettingsGui.BoolSettingFactory capAnvilCostFactory; // L character + private BoolSettingsGui.BoolSettingFactory capAnvilCost; // L character private GuiItem noCapRepairItem; - private IntSettingsGui.IntSettingFactory maxAnvilCostFactory; // C character + private IntSettingsGui.IntSettingFactory maxAnvilCost; // C character private GuiItem noMaxCostItem; private BoolSettingsGui.BoolSettingFactory removeAnvilCostLimit; // R character @@ -82,38 +88,46 @@ public class BasicConfigGui extends ValueUpdatableGui { private IntSettingsGui.IntSettingFactory itemRenameCost; // r character private IntSettingsGui.IntSettingFactory sacrificeIllegalEnchantCost; // S character + private BoolSettingsGui.BoolSettingFactory allowColorCode; // c character + private BoolSettingsGui.BoolSettingFactory allowHexColor; // h character + + private BoolSettingsGui.BoolSettingFactory permissionNeededForColor; // p character + private GuiItem noPermissionNeededItem; + private IntSettingsGui.IntSettingFactory useOfColorCost; // P character + private GuiItem noColorCostItem; + /** * Prepare basic gui displayed items factory and static items.. */ protected void prepareValues() { // cap anvil cost - this.capAnvilCostFactory = BoolSettingsGui.boolFactory("\u00A78Cap Anvil Cost ?", this, + this.capAnvilCost = new BoolSettingsGui.BoolSettingFactory("§8Cap Anvil Cost ?", this, ConfigHolder.DEFAULT_CONFIG, ConfigOptions.CAP_ANVIL_COST, ConfigOptions.DEFAULT_CAP_ANVIL_COST, - "\u00A77All anvil cost will be capped to \u00A7aMax Anvil Cost\u00A77 if enabled.", - "\u00A77In other words:", - "\u00A77For any anvil cost greater than \u00A7aMax Anvil Cost\u00A77, Cost will be set to \u00A7aMax Anvil Cost\u00A77."); + "§7All anvil cost will be capped to §aMax Anvil Cost§7 if enabled.", + "§7In other words:", + "§7For any anvil cost greater than §aMax Anvil Cost§7, Cost will be set to §aMax Anvil Cost§7."); // cap anvil cost not needed ItemStack item = new ItemStack(Material.BARRIER); ItemMeta meta = item.getItemMeta(); assert meta != null; - meta.setDisplayName("\u00A7cCap Anvil Cost ?"); - meta.setLore(Collections.singletonList("\u00A77This config only work if \u00A7cLimit Repair Cost\u00A77 is disabled.")); + meta.setDisplayName("§cCap Anvil Cost ?"); + meta.setLore(Collections.singletonList("§7This config only work if §cLimit Repair Cost§7 is disabled.")); item.setItemMeta(meta); this.noCapRepairItem = new GuiItem(item, GuiGlobalActions.stayInPlace, CustomAnvil.instance); // repair cost item IntRange range = ConfigOptions.MAX_ANVIL_COST_RANGE; - this.maxAnvilCostFactory = IntSettingsGui.intFactory("\u00A78Max Anvil Cost", this, + this.maxAnvilCost = new IntSettingsGui.IntSettingFactory("§8Max Anvil Cost", this, ConfigOptions.MAX_ANVIL_COST, ConfigHolder.DEFAULT_CONFIG, Arrays.asList( - "\u00A77Max cost the Anvil can get to.", - "\u00A77Valid values include \u00A7e0 \u00A77to \u00A7e1000\u00A77.", - "\u00A77Cost will be displayed as \u00A7cToo Expensive\u00A77:", - "\u00A77- If Cost is above \u00A7e39", - "\u00A77- And \u00A7eReplace Too Expensive\u00A77 is disabled" + "§7Max cost the Anvil can get to.", + "§7Valid values include §e0 §7to §e1000§7.", + "§7Cost will be displayed as §cToo Expensive§7:", + "§7- If Cost is above §e39", + "§7- And §eReplace Too Expensive§7 is disabled" ), range.getFirst(), range.getLast(), ConfigOptions.DEFAULT_MAX_ANVIL_COST, @@ -123,45 +137,49 @@ public class BasicConfigGui extends ValueUpdatableGui { meta = item.getItemMeta(); assert meta != null; - meta.setDisplayName("\u00A7cMax Anvil Cost"); - meta.setLore(Collections.singletonList("\u00A77This config only work if \u00A7cLimit Repair Cost\u00A77 is disabled.")); + meta.setDisplayName("§cMax Anvil Cost"); + meta.setLore(Collections.singletonList("§7This config only work if §cLimit Repair Cost§7 is disabled.")); item.setItemMeta(meta); this.noMaxCostItem = new GuiItem(item, GuiGlobalActions.stayInPlace, CustomAnvil.instance); // remove repair limit item - this.removeAnvilCostLimit = BoolSettingsGui.boolFactory("\u00A78Remove Anvil Cost Limit ?", this, + this.removeAnvilCostLimit = new BoolSettingsGui.BoolSettingFactory("§8Remove Anvil Cost Limit ?", this, ConfigHolder.DEFAULT_CONFIG, ConfigOptions.REMOVE_ANVIL_COST_LIMIT, ConfigOptions.DEFAULT_REMOVE_ANVIL_COST_LIMIT, - "\u00A77Whether the anvil's cost limit should be removed entirely.", - "\u00A77The anvil will still visually display \u00A7cToo Expensive\u00A77 if \u00A7eReplace Too Expensive\u00A77 is disabled.", - "\u00A77However, the action will be completable if xp requirement is meet."); + "§7Whether the anvil's cost limit should be removed entirely.", + "§7The anvil will still visually display §cToo Expensive§7 if §eReplace Too Expensive§7 is disabled.", + "§7However, the action will be completable if xp requirement is meet."); // replace too expensive item - this.replaceTooExpensive = BoolSettingsGui.boolFactory("\u00A78Replace Too Expensive ?", this, + this.replaceTooExpensive = new BoolSettingsGui.BoolSettingFactory("§8Replace Too Expensive ?", this, ConfigHolder.DEFAULT_CONFIG, ConfigOptions.REPLACE_TOO_EXPENSIVE, ConfigOptions.DEFAULT_REPLACE_TOO_EXPENSIVE, getReplaceToExpensiveLore()); + // ------------ + // Cost config + // ------------ + // item repair cost range = ConfigOptions.REPAIR_COST_RANGE; - this.itemRepairCost = IntSettingsGui.intFactory("\u00A78Item Repair Cost", this, + this.itemRepairCost = new IntSettingsGui.IntSettingFactory("§8Item Repair Cost", this, ConfigOptions.ITEM_REPAIR_COST, ConfigHolder.DEFAULT_CONFIG, Arrays.asList( - "\u00A77XP Level amount added to the anvil when the item", - "\u00A77is repaired by another item of the same type." + "§7XP Level amount added to the anvil when the item", + "§7is repaired by another item of the same type." ), range.getFirst(), range.getLast(), ConfigOptions.DEFAULT_ITEM_REPAIR_COST, 1, 5, 10, 50, 100); // unit repair cost - this.unitRepairCost = IntSettingsGui.intFactory("\u00A78Unit Repair Cost", this, + this.unitRepairCost = new IntSettingsGui.IntSettingFactory("§8Unit Repair Cost", this, ConfigOptions.UNIT_REPAIR_COST, ConfigHolder.DEFAULT_CONFIG, Arrays.asList( - "\u00A77XP Level amount added to the anvil when the item is repaired by an \u00A7eunit\u00A77.", - "\u00A77For example: a Diamond on a Diamond Sword.", - "\u00A77What's considered unit for what can be edited on the unit repair configuration." + "§7XP Level amount added to the anvil when the item is repaired by an §eunit§7.", + "§7For example: a Diamond on a Diamond Sword.", + "§7What's considered unit for what can be edited on the unit repair configuration." ), range.getFirst(), range.getLast(), ConfigOptions.DEFAULT_UNIT_REPAIR_COST, @@ -169,10 +187,10 @@ public class BasicConfigGui extends ValueUpdatableGui { // item rename cost range = ConfigOptions.ITEM_RENAME_COST_RANGE; - this.itemRenameCost = IntSettingsGui.intFactory("\u00A78Rename Cost", this, + this.itemRenameCost = new IntSettingsGui.IntSettingFactory("§8Rename Cost", this, ConfigOptions.ITEM_RENAME_COST, ConfigHolder.DEFAULT_CONFIG, Arrays.asList( - "\u00A77XP Level amount added to the anvil when the item is renamed." + "§7XP Level amount added to the anvil when the item is renamed." ), range.getFirst(), range.getLast(), ConfigOptions.DEFAULT_ITEM_RENAME_COST, @@ -180,29 +198,94 @@ public class BasicConfigGui extends ValueUpdatableGui { // sacrifice illegal enchant cost range = ConfigOptions.SACRIFICE_ILLEGAL_COST_RANGE; - this.sacrificeIllegalEnchantCost = IntSettingsGui.intFactory("\u00A78Sacrifice Illegal Enchant Cost", this, + this.sacrificeIllegalEnchantCost = new IntSettingsGui.IntSettingFactory("§8Sacrifice Illegal Enchant Cost", this, ConfigOptions.SACRIFICE_ILLEGAL_COST, ConfigHolder.DEFAULT_CONFIG, Arrays.asList( - "\u00A77XP Level amount added to the anvil when a sacrifice enchantment", - "\u00A77conflict With one of the left item enchantment" + "§7XP Level amount added to the anvil when a sacrifice enchantment", + "§7conflict With one of the left item enchantment" ), range.getFirst(), range.getLast(), ConfigOptions.DEFAULT_SACRIFICE_ILLEGAL_COST, 1, 5, 10, 50, 100); + // ------------- + // Color config + // ------------- + + // Allow us of color code + this.allowColorCode = new BoolSettingsGui.BoolSettingFactory("§8Allow Use Of Color Code ?", this, + ConfigHolder.DEFAULT_CONFIG, + ConfigOptions.ALLOW_COLOR_CODE, ConfigOptions.DEFAULT_ALLOW_COLOR_CODE, + "§7Whether players can use color code.", + "§7Color code a formatted like §a&a§7 and is used in the rename field of the anvil.", + "§7Player may need permission to use color code if §ePlayer need permission to use color§7 is enabled."); + + // Allow us of hexadecimal color + this.allowHexColor = new BoolSettingsGui.BoolSettingFactory("§8Allow Use Of Hexadecimal Color ?", this, + ConfigHolder.DEFAULT_CONFIG, + ConfigOptions.ALLOW_HEXADECIMAL_COLOR, ConfigOptions.DEFAULT_ALLOW_HEXADECIMAL_COLOR, + "§7Whether players can use hexadecimal color.", + "§7Color code a formatted like §2#012345 §7and is used in the rename field of the anvil.", + "§7Player may need permission to use color code if §ePermission Needed For Color§7 is enabled."); + + // Permission needed for color + this.permissionNeededForColor = new BoolSettingsGui.BoolSettingFactory("§8Need Permission To Use Color ?", this, + ConfigHolder.DEFAULT_CONFIG, + ConfigOptions.PERMISSION_NEEDED_FOR_COLOR, ConfigOptions.DEFAULT_PERMISSION_NEEDED_FOR_COLOR, + "§7Whether players should have permission to be able to use colors.", + "§7Give player §eca.color.code§7 Permission to allow use of color code.", + "§7Give player §eca.color.hex§7 Permission to allow use of hexadecimal color."); + + // Permission needed for color not necessary + item = new ItemStack(Material.BARRIER); + meta = item.getItemMeta(); + assert meta != null; + + meta.setDisplayName("§cNeed Permission To Use Color ?"); + meta.setLore(Arrays.asList("§7This config can do something only if one of the following config is enabled:", + "§7- §aAllow Use Of Color Code", + "§7- §aAllow Use Of Hexadecimal Color")); + item.setItemMeta(meta); + this.noPermissionNeededItem = new GuiItem(item, GuiGlobalActions.stayInPlace, CustomAnvil.instance); + + // Cost of using color + range = ConfigOptions.USE_OF_COLOR_COST_RANGE; + this.useOfColorCost = new IntSettingsGui.IntSettingFactory("§8Cost Of Using Color", this, + ConfigOptions.USE_OF_COLOR_COST, ConfigHolder.DEFAULT_CONFIG, + Arrays.asList( + "§7XP level cost when using color code or hexadecimal color using the anvil.", + "§7conflict With one of the left item enchantment" + ), + range.getFirst(), range.getLast(), + ConfigOptions.DEFAULT_USE_OF_COLOR_COST, + 1, 5, 10, 50, 100); + + // Permission needed for color not necessary + item = new ItemStack(Material.BARRIER); + meta = item.getItemMeta(); + assert meta != null; + + meta.setDisplayName("§cCost Of Using Color"); + meta.setLore(Arrays.asList("§7This config can do something only if one of the following config is enabled:", + "§7- §aAllow Use Of Color Code", + "§7- §aAllow Use Of Hexadecimal Color")); + item.setItemMeta(meta); + this.noColorCostItem = new GuiItem(item, GuiGlobalActions.stayInPlace, CustomAnvil.instance); + } @NotNull private String[] getReplaceToExpensiveLore() { ArrayList lore = new ArrayList<>(); - lore.add("\u00A77Whenever anvil cost is above \u00A7e39\u00A77 should display the true price and not \u00A7cToo Expensive\u00A77."); - lore.add("\u00A77However, when bypassing \u00A7cToo Expensive\u00A77, anvil price will be displayed as \u00A7aGreen\u00A77."); - lore.add("\u00A77Even if cost is displayed as \u00A7aGreen\u00A77:"); - lore.add("\u00A77If the player do not have the required xp level, the action will not be completable."); + lore.add("§7Whenever anvil cost is above §e39§7 should display the true price and not §cToo Expensive§7."); + lore.add("§7However, when bypassing §cToo Expensive§7, anvil price will be displayed as §aGreen§7."); + lore.add("§7Even if cost is displayed as §aGreen§7:"); + lore.add("§7If the player do not have the required xp level, the action will not be completable."); - if(!this.packetManager.isProtocoLibInstalled()){ + if(!this.packetManager.getCanSetInstantBuild()){ lore.add(""); - lore.add("\u00A74/!\\\u00A7cCaution/!\\ \u00A7cYou need ProtocoLib installed 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."); } String[] loreAsArray = new String[lore.size()]; @@ -215,8 +298,8 @@ public class BasicConfigGui extends ValueUpdatableGui { GuiItem capAnvilCostItem; GuiItem maxAnvilCostItem; if (!this.removeAnvilCostLimit.getConfiguredValue()) { - capAnvilCostItem = this.capAnvilCostFactory.getItem("Cap Anvil Cost"); - maxAnvilCostItem = this.maxAnvilCostFactory.getItem(Material.EXPERIENCE_BOTTLE, "Max Anvil Cost"); + capAnvilCostItem = this.capAnvilCost.getItem("Cap Anvil Cost"); + maxAnvilCostItem = this.maxAnvilCost.getItem(Material.EXPERIENCE_BOTTLE, "Max Anvil Cost"); } else { capAnvilCostItem = this.noCapRepairItem; maxAnvilCostItem = this.noMaxCostItem; @@ -243,14 +326,46 @@ public class BasicConfigGui extends ValueUpdatableGui { pane.bindItem('U', unitRepairCostItem); // item rename cost - GuiItem itemRenameCost = this.itemRenameCost.getItem(Material.NAME_TAG); - pane.bindItem('r', itemRenameCost); + GuiItem itemRenameCostItem = this.itemRenameCost.getItem(Material.NAME_TAG); + pane.bindItem('r', itemRenameCostItem); // sacrifice illegal enchant cost GuiItem illegalCostItem = this.sacrificeIllegalEnchantCost.getItem(Material.ENCHANTED_BOOK); pane.bindItem('S', illegalCostItem); + // work penalty type + GuiItem workPenaltyType = WorkPenaltyTypeSettingGui.getDisplayItem(this, Material.DAMAGED_ANVIL, "§aWork Penalty Type"); + pane.bindItem('W', workPenaltyType); + + // allow color code + GuiItem allowColorCodeItem = this.allowColorCode.getItem(); + pane.bindItem('c', allowColorCodeItem); + + // allow hex color + GuiItem allowHexColorItem = this.allowHexColor.getItem(); + pane.bindItem('h', allowHexColorItem); + + // True if player could place color + if(ConfigOptions.INSTANCE.getRenameColorPossible()){ + // use permission for color + GuiItem permissionNeededItem = this.permissionNeededForColor.getItem(); + pane.bindItem('p', permissionNeededItem); + + // using color cost + GuiItem useColorCostItem = this.useOfColorCost.getItem(Material.EXPERIENCE_BOTTLE, "Use color"); + pane.bindItem('P', useColorCostItem); + }else{ + pane.bindItem('p', this.noPermissionNeededItem); + pane.bindItem('P', this.noColorCostItem); + } + + update(); } + @Override + public Gui getConnectedGui() { + return this; + } + } 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 d0c0ec2..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 @@ -5,63 +5,82 @@ import org.bukkit.Material; import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemStack; 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.gui.config.list.MappedGuiListConfigGui; import xyz.alexcrea.cuanvil.gui.config.list.elements.CustomRecipeSubSettingGui; +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.List; +import java.util.ArrayList; +import java.util.Collection; -public class CustomRecipeConfigGui extends MappedGuiListConfigGui { +public class CustomRecipeConfigGui extends MappedGuiListConfigGui> { + private static CustomRecipeConfigGui INSTANCE = new CustomRecipeConfigGui(); - public final static CustomRecipeConfigGui INSTANCE = new CustomRecipeConfigGui(); + @Nullable + public static CustomRecipeConfigGui getCurrentInstance() { + return INSTANCE; + } - static { - INSTANCE.init(); + @NotNull + public static CustomRecipeConfigGui getInstance() { + if (INSTANCE == null) INSTANCE = new CustomRecipeConfigGui(); + + return INSTANCE; } private CustomRecipeConfigGui() { super("Custom Recipe Config"); + init(); } @Override protected ItemStack createItemForGeneric(AnvilCustomRecipe recipe) { // Get base item to display ItemStack craftResultItem = recipe.getResultItem(); - ItemStack displaydItem; - if(craftResultItem == null){ - displaydItem = new ItemStack(Material.BARRIER); - }else{ - displaydItem = craftResultItem.clone(); + ItemStack displayedItem; + if (craftResultItem == null) { + displayedItem = new ItemStack(Material.BARRIER); + } else { + displayedItem = craftResultItem.clone(); } // edit displayed item - ItemMeta meta = displaydItem.getItemMeta(); + ItemMeta meta = displayedItem.getItemMeta(); assert meta != null; - meta.setDisplayName("\u00A7e" + CasedStringUtil.snakeToUpperSpacedCase(recipe.toString()) + " \u00A7fCustom recipe"); + meta.setDisplayName("§e" + CasedStringUtil.snakeToUpperSpacedCase(recipe.toString()) + " §fCustom recipe"); meta.addItemFlags(ItemFlag.values()); + meta.setLore(getRecipeLore(recipe)); + + displayedItem.setItemMeta(meta); + return displayedItem; + } + + private static @NotNull ArrayList getRecipeLore(AnvilCustomRecipe recipe) { boolean shouldWork = recipe.validate(); - meta.setLore(Arrays.asList( - "\u00A77Should work: \u00A7"+(shouldWork ? "aYes" : "cNo"), - "\u00A77Exact count: \u00A7"+(recipe.getExactCount() ? "aYes" : "cNo"), - "\u00A77Recipe Xp Cost: \u00A7e"+recipe.getXpCostPerCraft() - - )); - - displaydItem.setItemMeta(meta); - return displaydItem; + 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 CustomRecipeSubSettingGui newInstanceOfGui(AnvilCustomRecipe generic, GuiItem item) { - return new CustomRecipeSubSettingGui(this, generic, item); + protected LazyElement newInstanceOfGui(AnvilCustomRecipe generic, GuiItem item) { + return new LazyElement<>(item, () -> new CustomRecipeSubSettingGui(this, generic)); } @Override @@ -75,7 +94,11 @@ public class CustomRecipeConfigGui extends MappedGuiListConfigGui getEveryDisplayableInstanceOfGeneric() { + protected Collection getEveryDisplayableInstanceOfGeneric() { return ConfigHolder.CUSTOM_RECIPE_HOLDER.getRecipeManager().getRecipeList(); } } diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/EnchantConflictGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/EnchantConflictGui.java index 06b9bc8..912e6cb 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/EnchantConflictGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/EnchantConflictGui.java @@ -5,6 +5,8 @@ import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemStack; 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.group.EnchantConflictGroup; import xyz.alexcrea.cuanvil.group.IncludeGroup; @@ -14,18 +16,30 @@ import xyz.alexcrea.cuanvil.gui.util.GuiSharedConstant; import xyz.alexcrea.cuanvil.util.CasedStringUtil; import java.util.Arrays; -import java.util.List; +import java.util.Collection; -public class EnchantConflictGui extends MappedGuiListConfigGui { +public class EnchantConflictGui extends MappedGuiListConfigGui> { - public final static EnchantConflictGui INSTANCE = new EnchantConflictGui(); + private static EnchantConflictGui INSTANCE; - static { - INSTANCE.init(); + @Nullable + public static EnchantConflictGui getCurrentInstance(){ + return INSTANCE; } + @NotNull + public static EnchantConflictGui getInstance(){ + if(INSTANCE == null) INSTANCE = new EnchantConflictGui(); + + return INSTANCE; + } + + private EnchantConflictGui() { super( "Conflict Config"); + + init(); } @Override @@ -36,7 +50,7 @@ public class EnchantConflictGui extends MappedGuiListConfigGui newInstanceOfGui(EnchantConflictGroup conflict, GuiItem item) { + return new LazyElement<>(item, () -> new EnchantConflictSubSettingGui(this, conflict)); } @Override @@ -83,7 +97,7 @@ public class EnchantConflictGui extends MappedGuiListConfigGui getEveryDisplayableInstanceOfGeneric() { + protected Collection getEveryDisplayableInstanceOfGeneric() { return ConfigHolder.CONFLICT_HOLDER.getConflictManager().getConflictList(); } diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/EnchantCostConfigGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/EnchantCostConfigGui.java index 928b795..a614536 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/EnchantCostConfigGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/EnchantCostConfigGui.java @@ -4,10 +4,11 @@ import com.github.stefvanschie.inventoryframework.gui.GuiItem; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.Nullable; import xyz.alexcrea.cuanvil.config.ConfigHolder; +import xyz.alexcrea.cuanvil.enchant.CAEnchantment; import xyz.alexcrea.cuanvil.enchant.EnchantmentProperties; import xyz.alexcrea.cuanvil.enchant.EnchantmentRarity; -import xyz.alexcrea.cuanvil.enchant.WrappedEnchantment; import xyz.alexcrea.cuanvil.gui.config.settings.EnchantCostSettingsGui; import xyz.alexcrea.cuanvil.gui.util.GuiGlobalItems; import xyz.alexcrea.cuanvil.util.CasedStringUtil; @@ -22,51 +23,46 @@ import java.util.Locale; */ public class EnchantCostConfigGui extends AbstractEnchantConfigGui { - private final static String SECTION_NAME = "enchant_values"; + private static final String SECTION_NAME = "enchant_values"; - public final static EnchantCostConfigGui INSTANCE = new EnchantCostConfigGui(); + private static EnchantCostConfigGui INSTANCE = null; - static { - INSTANCE.init(); + @Nullable + public static EnchantCostConfigGui getInstance() { + return INSTANCE; } /** * Constructor of this Global gui for enchantment cost settings. */ - private EnchantCostConfigGui() { - super("\u00A78Enchantment Level Cost"); + public EnchantCostConfigGui() { + super("§8Enchantment Level Cost"); + if(INSTANCE == null) INSTANCE = this; + init(); } @Override - public EnchantCostSettingsGui.EnchantCostSettingFactory getFactoryFromEnchant(WrappedEnchantment enchant) { - String key = enchant.getKey().getKey().toLowerCase(Locale.ENGLISH); - String prettyKey = CasedStringUtil.snakeToUpperSpacedCase(key); + public EnchantCostSettingsGui.EnchantCostSettingFactory createFactory(CAEnchantment enchant) { + String key = enchant.getKey().toString().toLowerCase(Locale.ENGLISH); + String prettyKey = CasedStringUtil.snakeToUpperSpacedCase(key.replace(":", "_")); - // try to find rarity. default to 0 if not found - EnchantmentRarity rarity = enchant.defaultRarity(); - try { - rarity = EnchantmentProperties.valueOf(key.toUpperCase(Locale.ENGLISH)).getRarity(); - } catch (IllegalArgumentException ignored) { - } - - return EnchantCostSettingsGui.enchantCostFactory(prettyKey + " Level Cost", this, - ConfigHolder.DEFAULT_CONFIG, SECTION_NAME + '.' + key, + return new EnchantCostSettingsGui.EnchantCostSettingFactory(prettyKey + " Cost", this, + SECTION_NAME + '.' + key, ConfigHolder.DEFAULT_CONFIG, Arrays.asList( - "\u00A77How many level should " + prettyKey, - "\u00A77cost when applied by book or by another item." + "§7How many level should " + prettyKey, + "§7cost when applied by book or by another item." ), - 0, 255, - rarity.getItemValue(), rarity.getBookValue(), + enchant, 0, 255, 1, 10, 50); } @Override - public GuiItem getItemFromFactory(EnchantCostSettingsGui.EnchantCostSettingFactory factory) { + public GuiItem itemFromFactory(CAEnchantment enchantment, EnchantCostSettingsGui.EnchantCostSettingFactory factory) { // Get item properties int itemCost = factory.getConfiguredValue(); int bookCost = factory.getConfiguredBookValue(); - String itemName = "\u00A7a" + factory.getTitle(); + String itemName = "§a" + factory.getTitle(); // Create item ItemStack item = new ItemStack(Material.ENCHANTED_BOOK); ItemMeta itemMeta = item.getItemMeta(); @@ -74,8 +70,8 @@ public class EnchantCostConfigGui extends AbstractEnchantConfigGui lore = new ArrayList<>(); - lore.add("\u00A77Item Cost: \u00A7e" + itemCost); - lore.add("\u00A77Book Cost: \u00A7e" + bookCost); + lore.add("§7Item Cost: §e" + itemCost); + lore.add("§7Book Cost: §e" + bookCost); List displayLore = factory.getDisplayLore(); if(!displayLore.isEmpty()){ diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/EnchantLimitConfigGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/EnchantLimitConfigGui.java index c9d82f3..e9edbeb 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/EnchantLimitConfigGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/EnchantLimitConfigGui.java @@ -1,9 +1,11 @@ package xyz.alexcrea.cuanvil.gui.config.global; import com.github.stefvanschie.inventoryframework.gui.GuiItem; +import io.delilaheve.util.ConfigOptions; import org.bukkit.Material; +import org.jetbrains.annotations.Nullable; import xyz.alexcrea.cuanvil.config.ConfigHolder; -import xyz.alexcrea.cuanvil.enchant.WrappedEnchantment; +import xyz.alexcrea.cuanvil.enchant.CAEnchantment; import xyz.alexcrea.cuanvil.gui.config.settings.IntSettingsGui; import xyz.alexcrea.cuanvil.util.CasedStringUtil; @@ -15,39 +17,64 @@ import java.util.Locale; */ public class EnchantLimitConfigGui extends AbstractEnchantConfigGui { - private final static String SECTION_NAME = "enchant_limits"; + private static final String SECTION_NAME = ConfigOptions.ENCHANT_LIMIT_ROOT; - public final static EnchantLimitConfigGui INSTANCE = new EnchantLimitConfigGui(); + private static EnchantLimitConfigGui INSTANCE = null; - static { - INSTANCE.init(); + @Nullable + public static EnchantLimitConfigGui getInstance() { + return INSTANCE; } /** * Constructor of this Global gui for enchantment level limit settings. */ - private EnchantLimitConfigGui() { - super("\u00A78Enchantment Level Limit"); + public EnchantLimitConfigGui() { + super("§8Enchantment Level Limit"); + if(INSTANCE == null) INSTANCE = this; + init(); } @Override - public IntSettingsGui.IntSettingFactory getFactoryFromEnchant(WrappedEnchantment enchant) { - String key = enchant.getKey().getKey().toLowerCase(Locale.ROOT); - String prettyKey = CasedStringUtil.snakeToUpperSpacedCase(key); + public IntSettingsGui.IntSettingFactory createFactory(CAEnchantment enchant) { + String key = enchant.getKey().toString().toLowerCase(Locale.ROOT); + String prettyKey = CasedStringUtil.snakeToUpperSpacedCase(key.replace(":", "_")); - return IntSettingsGui.intFactory(prettyKey + " Level Limit", this, + var defaultValue = enchant.defaultMaxLevel(); + + return new IntSettingsGui.IntSettingFactory(prettyKey + " Limit", this, SECTION_NAME + '.' + key, ConfigHolder.DEFAULT_CONFIG, Collections.singletonList( - "\u00A77Maximum applied level of " + prettyKey + "§7Maximum applied level of " + prettyKey ), - 0, 255, - enchant.defaultMaxLevel(), - 1, 5, 10, 50, 100); + -1, 255, -1, + 1, 5, 10, 50, 100){ + + @Override + public int getConfiguredValue() { + var value = ConfigOptions.INSTANCE.rawEnchantLimit(enchant); + return Math.min(value, ConfigOptions.ENCHANT_LIMIT); + } + + @Override + public String valueDisplayName(IntSettingsGui.ValueDisplayType type, int value) { + + if(value < 0) { + return switch (type) { + case CURRENT -> "Default (" + defaultValue + ")"; + case RESET -> String.valueOf(defaultValue); + default -> "Default"; + }; + + } + else return super.valueDisplayName(type, value); + } + }; } @Override - public GuiItem getItemFromFactory(IntSettingsGui.IntSettingFactory inventoryFactory) { + public GuiItem itemFromFactory(CAEnchantment enchantment, IntSettingsGui.IntSettingFactory inventoryFactory) { return inventoryFactory.getItem( Material.ENCHANTED_BOOK, inventoryFactory.getTitle()); diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/EnchantMergeLimitConfigGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/EnchantMergeLimitConfigGui.java new file mode 100644 index 0000000..0d391e7 --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/EnchantMergeLimitConfigGui.java @@ -0,0 +1,67 @@ +package xyz.alexcrea.cuanvil.gui.config.global; + +import com.github.stefvanschie.inventoryframework.gui.GuiItem; +import io.delilaheve.util.ConfigOptions; +import org.bukkit.Material; +import org.jetbrains.annotations.Nullable; +import xyz.alexcrea.cuanvil.config.ConfigHolder; +import xyz.alexcrea.cuanvil.enchant.CAEnchantment; +import xyz.alexcrea.cuanvil.gui.config.settings.IntSettingsGui; +import xyz.alexcrea.cuanvil.util.CasedStringUtil; + +import java.util.Arrays; +import java.util.Locale; + +public class EnchantMergeLimitConfigGui extends AbstractEnchantConfigGui { + + private static final String SECTION_NAME = "disable-merge-over"; + + private static EnchantMergeLimitConfigGui INSTANCE = null; + + @Nullable + public static EnchantMergeLimitConfigGui getInstance() { + return INSTANCE; + } + + /** + * Constructor of this Global gui for enchantment level limit settings. + */ + public EnchantMergeLimitConfigGui() { + super("§8Enchantment Maximum Merge Level"); + if(INSTANCE == null) INSTANCE = this; + + init(); + } + + @Override + public IntSettingsGui.IntSettingFactory createFactory(CAEnchantment enchant) { + String key = enchant.getKey().toString().toLowerCase(Locale.ROOT); + String prettyKey = CasedStringUtil.snakeToUpperSpacedCase(key.replace(":", "_")); + + return new IntSettingsGui.IntSettingFactory(prettyKey + " Merge Limit", this, + SECTION_NAME + '.' + key, ConfigHolder.DEFAULT_CONFIG, + Arrays.asList( + "§7Maximum merge level for for " + prettyKey, + "", + "§7For example, if set to §e2§7, §alvl1 §7+ §alvl1 §7of will give a §alvl2", + "§7But §alvl2 §7+ §alvl2 §7will not give a §clv3§7.", + "§7Will still not merge above max enchantment level", + "§e-1 §7(default) will set the merge limit to enchantment's maximum level" + ), + -1, 255, -1, + 1, 5, 10, 50, 100){ + + @Override + public int getConfiguredValue() { + return ConfigOptions.INSTANCE.maxBeforeMergeDisabled(enchant); + } + }; + } + + @Override + public GuiItem itemFromFactory(CAEnchantment enchantment, IntSettingsGui.IntSettingFactory inventoryFactory) { + return inventoryFactory.getItem( + Material.ENCHANTED_BOOK, + inventoryFactory.getTitle()); + } +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/GroupConfigGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/GroupConfigGui.java index 633f219..8e20751 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/GroupConfigGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/GroupConfigGui.java @@ -5,6 +5,8 @@ import org.bukkit.configuration.ConfigurationSection; import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemStack; 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.group.AbstractMaterialGroup; import xyz.alexcrea.cuanvil.group.GroupType; @@ -13,21 +15,32 @@ import xyz.alexcrea.cuanvil.group.ItemGroupManager; import xyz.alexcrea.cuanvil.gui.config.list.MappedGuiListConfigGui; import xyz.alexcrea.cuanvil.gui.config.list.elements.GroupConfigSubSettingGui; import xyz.alexcrea.cuanvil.util.CasedStringUtil; +import xyz.alexcrea.cuanvil.util.LazyValue; import java.util.ArrayList; import java.util.Arrays; -import java.util.List; +import java.util.Collection; -public class GroupConfigGui extends MappedGuiListConfigGui { +public class GroupConfigGui extends MappedGuiListConfigGui> { - public final static GroupConfigGui INSTANCE = new GroupConfigGui(); + private static GroupConfigGui INSTANCE; - static { - INSTANCE.init(); + @Nullable + public static GroupConfigGui getCurrentInstance(){ + return INSTANCE; + } + + @NotNull + public static GroupConfigGui getInstance(){ + if(INSTANCE == null) INSTANCE = new GroupConfigGui(); + + return INSTANCE; } public GroupConfigGui() { super("Group Config"); + + init(); } @Override @@ -37,19 +50,19 @@ public class GroupConfigGui extends MappedGuiListConfigGui getEveryDisplayableInstanceOfGeneric() { + protected Collection getEveryDisplayableInstanceOfGeneric() { ArrayList includeGroups = new ArrayList<>(); for (AbstractMaterialGroup group : ConfigHolder.ITEM_GROUP_HOLDER.getItemGroupsManager().getGroupMap().values()) { @@ -61,8 +74,8 @@ public class GroupConfigGui extends MappedGuiListConfigGui newInstanceOfGui(IncludeGroup group, GuiItem item) { + return new LazyElement<>(item, () -> new GroupConfigSubSettingGui(this, group)); } @Override diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/UnitRepairConfigGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/UnitRepairConfigGui.java index 6bd4c96..0e366ae 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/UnitRepairConfigGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/UnitRepairConfigGui.java @@ -6,6 +6,8 @@ import org.bukkit.Material; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.inventory.ItemStack; 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.gui.config.ask.SelectItemTypeGui; import xyz.alexcrea.cuanvil.gui.config.list.MappedGuiListConfigGui; @@ -14,25 +16,38 @@ import xyz.alexcrea.cuanvil.util.CasedStringUtil; import java.util.ArrayList; import java.util.Arrays; -import java.util.List; +import java.util.Collection; -public class UnitRepairConfigGui extends MappedGuiListConfigGui { +public class UnitRepairConfigGui extends + MappedGuiListConfigGui> { - public final static UnitRepairConfigGui INSTANCE = new UnitRepairConfigGui(); + private static UnitRepairConfigGui INSTANCE; - static { - INSTANCE.init(); + @Nullable + public static UnitRepairConfigGui getCurrentInstance(){ + return INSTANCE; + } + + @NotNull + public static UnitRepairConfigGui getInstance(){ + if(INSTANCE == null) INSTANCE = new UnitRepairConfigGui(); + + return INSTANCE; } private UnitRepairConfigGui() { super("Unit Repair Config"); + + init(); } @Override - protected UnitRepairElementListGui newInstanceOfGui(Material material, GuiItem item) { - UnitRepairElementListGui element = new UnitRepairElementListGui(material, this, item); - element.init(); - return element; + protected LazyElement newInstanceOfGui(Material material, GuiItem item) { + return new LazyElement<>(item, () -> { + UnitRepairElementListGui element = new UnitRepairElementListGui(material, this); + element.init(); + return element; + }); } @Override @@ -50,10 +65,10 @@ public class UnitRepairConfigGui extends MappedGuiListConfigGui getEveryDisplayableInstanceOfGeneric() { + protected Collection getEveryDisplayableInstanceOfGeneric() { ArrayList materials = new ArrayList<>(); for (String matName : ConfigHolder.UNIT_REPAIR_HOLDER.getConfig().getKeys(false)) { @@ -81,21 +96,21 @@ public class UnitRepairConfigGui extends MappedGuiListConfigGui { + return new GuiItem(createItem, clickEvent -> { clickEvent.setCancelled(true); new SelectItemTypeGui( "Select unit repair item.", - "\u00A77Click here with an item to set the item\n" + - "\u00A77You like to be an unit repair item", + "§7Click here with an item to set the item\n" + + "§7You like to be an unit repair item", this, (itemStack, player) -> { Material type = itemStack.getType(); @@ -103,13 +118,25 @@ public class UnitRepairConfigGui extends MappedGuiListConfigGui getInstanceOrCreate(Material mat){ + LazyElement element = this.elementGuiMap.get(mat); + if(element == null){ + updateValueForGeneric(mat, false); + + element = this.elementGuiMap.get(mat); + } + + return element; + } + @Override // Not used in this implementation. protected String genericDisplayedName() { return "this function Should not be used."; diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/ElementListConfigGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/ElementListConfigGui.java index 2411bbe..f3cc0b4 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/ElementListConfigGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/ElementListConfigGui.java @@ -1,6 +1,8 @@ package xyz.alexcrea.cuanvil.gui.config.list; import com.github.stefvanschie.inventoryframework.gui.GuiItem; +import com.github.stefvanschie.inventoryframework.gui.type.ChestGui; +import com.github.stefvanschie.inventoryframework.gui.type.util.Gui; import com.github.stefvanschie.inventoryframework.pane.Orientable; import com.github.stefvanschie.inventoryframework.pane.OutlinePane; import com.github.stefvanschie.inventoryframework.pane.Pane; @@ -14,39 +16,43 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.jetbrains.annotations.NotNull; import xyz.alexcrea.cuanvil.gui.ValueUpdatableGui; -import xyz.alexcrea.cuanvil.gui.config.MainConfigGui; import xyz.alexcrea.cuanvil.gui.util.GuiGlobalItems; import xyz.alexcrea.cuanvil.gui.util.GuiSharedConstant; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; -import java.util.List; import java.util.UUID; -public abstract class ElementListConfigGui< T > extends ValueUpdatableGui { +public abstract class ElementListConfigGui< T > extends ChestGui implements ValueUpdatableGui { private final String namePrefix; protected PatternPane backgroundPane; - public ElementListConfigGui(@NotNull String title) { + public static final int LIST_FILLER_START_X = 1; + public static final int LIST_FILLER_START_Y = 1; + public static final int LIST_FILLER_LENGTH = 7; + public static final int LIST_FILLER_HEIGHT = 4; + + protected ElementListConfigGui(@NotNull String title, Gui parent) { super(6, title, CustomAnvil.instance); this.namePrefix = title; // Back item panel Pattern pattern = getBackgroundPattern(); this.backgroundPane = new PatternPane(0, 0, 9, 6, Pane.Priority.LOW, pattern); - GuiGlobalItems.addBackItem(this.backgroundPane, MainConfigGui.getInstance()); + GuiGlobalItems.addBackItem(this.backgroundPane, parent); } protected Pattern getBackgroundPattern(){ return new Pattern( - GuiSharedConstant.EMPTY_GUI_FULL_LINE, - GuiSharedConstant.EMPTY_GUI_FULL_LINE, - GuiSharedConstant.EMPTY_GUI_FULL_LINE, - GuiSharedConstant.EMPTY_GUI_FULL_LINE, - GuiSharedConstant.EMPTY_GUI_FULL_LINE, + GuiSharedConstant.UPPER_FILLER_FULL_PLANE, + GuiSharedConstant.EMPTY_FILLER_FULL_LINE, + GuiSharedConstant.EMPTY_FILLER_FULL_LINE, + GuiSharedConstant.EMPTY_FILLER_FULL_LINE, + GuiSharedConstant.EMPTY_FILLER_FULL_LINE, "B11L1R11C" ); } @@ -55,7 +61,7 @@ public abstract class ElementListConfigGui< T > extends ValueUpdatableGui { protected ArrayList pages; protected HashMap pageMap; - public void init() { // Why I'm using an init function ? + public void init() { // Why I'm using an init function ? //TODO determine why is it using a init function and not used on constructor. GuiGlobalItems.addBackgroundItem(this.backgroundPane); this.backgroundPane.bindItem('1', GuiSharedConstant.SECONDARY_BACKGROUND_ITEM); addPane(this.backgroundPane); @@ -77,7 +83,7 @@ public abstract class ElementListConfigGui< T > extends ValueUpdatableGui { protected void prepareStaticValues(){ // Left item creation for consumer & bind - this.goLeftItem = new GuiItem(new ItemStack(Material.RED_TERRACOTTA), event -> { + this.goLeftItem = new GuiItem(new ItemStack(Material.RED_STAINED_GLASS_PANE), event -> { HumanEntity viewer = event.getWhoClicked(); UUID playerUUID = viewer.getUniqueId(); int page = this.pageMap.getOrDefault(playerUUID, 0); @@ -92,7 +98,7 @@ public abstract class ElementListConfigGui< T > extends ValueUpdatableGui { }, CustomAnvil.instance); // Right item creation for consumer & bind - this.goRightItem = new GuiItem(new ItemStack(Material.GREEN_TERRACOTTA), event -> { + this.goRightItem = new GuiItem(new ItemStack(Material.LIME_STAINED_GLASS_PANE), event -> { HumanEntity viewer = event.getWhoClicked(); UUID playerUUID = viewer.getUniqueId(); int page = pageMap.getOrDefault(playerUUID, 0); @@ -116,8 +122,8 @@ public abstract class ElementListConfigGui< T > extends ValueUpdatableGui { this.pages.clear(); this.pages.add(this.firstPage); - for (T conflict : getEveryDisplayableInstanceOfGeneric()) { - updateValueForGeneric(conflict, false); + for (T generic : getEveryDisplayableInstanceOfGeneric()) { + updateValueForGeneric(generic, false); } update(); @@ -126,7 +132,7 @@ public abstract class ElementListConfigGui< T > extends ValueUpdatableGui { protected abstract GuiItem prepareCreateNewItem(); protected OutlinePane createEmptyPage() { - OutlinePane page = new OutlinePane(0, 0, 9, 5); + OutlinePane page = new OutlinePane(LIST_FILLER_START_X, LIST_FILLER_START_Y, LIST_FILLER_LENGTH, LIST_FILLER_HEIGHT); page.align(OutlinePane.Alignment.BEGIN); page.setOrientation(Orientable.Orientation.HORIZONTAL); @@ -144,7 +150,7 @@ public abstract class ElementListConfigGui< T > extends ValueUpdatableGui { protected void addToPage(GuiItem guiItem) { // Get first available page or create one OutlinePane page = this.pages.get(this.pages.size() - 1); - if (page.getItems().size() >= 5 * 9) { + if (page.getItems().size() >= LIST_FILLER_LENGTH * LIST_FILLER_HEIGHT) { page = createEmptyPage(); this.pages.add(page); } @@ -197,7 +203,7 @@ public abstract class ElementListConfigGui< T > extends ValueUpdatableGui { ItemStack leftItem = this.goLeftItem.getItem(); ItemMeta leftMeta = leftItem.getItemMeta(); - leftMeta.setDisplayName("\u00A7eReturn to page " + (page)); + leftMeta.setDisplayName("§eReturn to page " + (page)); leftItem.setItemMeta(leftMeta); this.goLeftItem.setItem(leftItem); @@ -214,7 +220,7 @@ public abstract class ElementListConfigGui< T > extends ValueUpdatableGui { ItemStack rightItem = this.goRightItem.getItem(); ItemMeta rightMeta = rightItem.getItemMeta(); - rightMeta.setDisplayName("\u00A7eGo to page " + (page + 2)); + rightMeta.setDisplayName("§eGo to page " + (page + 2)); rightItem.setItemMeta(rightMeta); this.goRightItem.setItem(rightItem); @@ -294,7 +300,7 @@ public abstract class ElementListConfigGui< T > extends ValueUpdatableGui { protected abstract void updateGeneric(T generic, ItemStack usedItem); - protected abstract List getEveryDisplayableInstanceOfGeneric(); + protected abstract Collection getEveryDisplayableInstanceOfGeneric(); @Override public void updateGuiValues() { @@ -304,4 +310,9 @@ public abstract class ElementListConfigGui< T > extends ValueUpdatableGui { reloadValues(); } + @Override + public Gui getConnectedGui() { + return this; + } + } diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/MappedElementListConfigGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/MappedElementListConfigGui.java index 3f2b7f5..31ab718 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/MappedElementListConfigGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/MappedElementListConfigGui.java @@ -7,6 +7,7 @@ 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.gui.config.MainConfigGui; import xyz.alexcrea.cuanvil.gui.util.GuiGlobalActions; import java.util.Arrays; @@ -16,8 +17,8 @@ import java.util.function.Consumer; public abstract class MappedElementListConfigGui< T, S > extends ElementListConfigGui< T > { protected final HashMap elementGuiMap; - public MappedElementListConfigGui(@NotNull String title) { - super(title); + protected MappedElementListConfigGui(@NotNull String title) { + super(title, MainConfigGui.getInstance()); this.elementGuiMap = new HashMap<>(); } @@ -29,16 +30,16 @@ public abstract class MappedElementListConfigGui< T, S > extends ElementListConf ItemMeta createMeta = createItem.getItemMeta(); assert createMeta != null; - createMeta.setDisplayName("\u00A7aCreate new "+genericDisplayedName()); + createMeta.setDisplayName("§aCreate new "+genericDisplayedName()); createMeta.setLore(Arrays.asList( - "\u00A77Create a new "+genericDisplayedName()+".", - "\u00A77You will be asked to name the "+genericDisplayedName()+" in chat.", - "\u00A77Then, you should edit the "+genericDisplayedName()+" config as you need" + "§7Create a new "+genericDisplayedName()+".", + "§7You will be asked to name the "+genericDisplayedName()+" in chat.", + "§7Then, you should edit the "+genericDisplayedName()+" config as you need" )); createItem.setItemMeta(createMeta); - return new GuiItem(createItem, (clickEvent) -> { + return new GuiItem(createItem, clickEvent -> { clickEvent.setCancelled(true); HumanEntity player = clickEvent.getWhoClicked(); @@ -50,8 +51,8 @@ public abstract class MappedElementListConfigGui< T, S > extends ElementListConf } player.closeInventory(); - player.sendMessage("\u00A7eWrite the "+genericDisplayedName()+" name you want to create in the chat.\n" + - "\u00A7eOr write \u00A7ccancel \u00A7eto go back to "+genericDisplayedName()+" config menu"); + player.sendMessage("§eWrite the "+genericDisplayedName()+" name you want to create in the chat.\n" + + "§eOr write §ccancel §eto go back to "+genericDisplayedName()+" config menu"); CustomAnvil.Companion.getChatListener().setListenedCallback(player, prepareCreateItemConsumer(player)); diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/MappedGuiListConfigGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/MappedGuiListConfigGui.java index 5438600..3aa18e0 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/MappedGuiListConfigGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/MappedGuiListConfigGui.java @@ -3,24 +3,31 @@ package xyz.alexcrea.cuanvil.gui.config.list; import com.github.stefvanschie.inventoryframework.gui.GuiItem; import io.delilaheve.CustomAnvil; import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryClickEvent; import org.jetbrains.annotations.NotNull; import xyz.alexcrea.cuanvil.gui.config.list.elements.ElementMappedToListGui; import xyz.alexcrea.cuanvil.gui.util.GuiGlobalActions; +import xyz.alexcrea.cuanvil.util.LazyValue; import java.util.Locale; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; +import java.util.function.Supplier; -public abstract class MappedGuiListConfigGui< T, S extends ElementMappedToListGui> extends MappedElementListConfigGui< T, S > { +public abstract class MappedGuiListConfigGui< T, S extends MappedGuiListConfigGui.LazyElement> + extends MappedElementListConfigGui< T, S > { - public MappedGuiListConfigGui(@NotNull String title) { + protected MappedGuiListConfigGui(@NotNull String title) { super(title); } @Override public void reloadValues() { - this.elementGuiMap.forEach((conflict, gui) -> gui.cleanAndBeUnusable()); + this.elementGuiMap.forEach((conflict, element) -> { + ElementMappedToListGui gui = element.getStored(); + if(gui != null) gui.cleanAndBeUnusable(); + }); this.elementGuiMap.clear(); super.reloadValues(); @@ -30,23 +37,24 @@ public abstract class MappedGuiListConfigGui< T, S extends ElementMappedToListGu protected S newElementRequested(T generic, GuiItem newItem) { S element = newInstanceOfGui(generic, newItem); - newItem.setAction(GuiGlobalActions.openGuiAction(element.getMappedGui())); + newItem.setAction(element.openAction()); return element; } @Override protected GuiItem findItemFromElement(T generic, S element) { - return element.getParentItemForThisGui(); + return element.getParentItem(); } @Override protected void updateElement(T generic, S element) { - element.updateLocal(); + ElementMappedToListGui gui = element.getStored(); + if(gui != null) gui.updateLocal(); } @Override protected GuiItem findGuiItemForRemoval(T generic, S element) { - return element.getParentItemForThisGui(); + return element.getParentItem(); } @Override @@ -74,7 +82,7 @@ public abstract class MappedGuiListConfigGui< T, S extends ElementMappedToListGu // Not the most efficient on large number of conflict, but it should not run often. for (T generic : getEveryDisplayableInstanceOfGeneric()) { if (generic.toString().equalsIgnoreCase(message)) { - player.sendMessage("\u00A7cPlease enter a "+genericDisplayedName()+" name that do not already exist..."); + player.sendMessage("§cPlease enter a "+genericDisplayedName()+" name that do not already exist..."); // wait next message. CustomAnvil.Companion.getChatListener().setListenedCallback(player, selfRef.get()); return; @@ -90,7 +98,7 @@ public abstract class MappedGuiListConfigGui< T, S extends ElementMappedToListGu updateValueForGeneric(generic, true); // show the new conflict config to the player - this.elementGuiMap.get(generic).getMappedGui().show(player); + this.elementGuiMap.get(generic).get().getMappedGui().show(player); update(); }; @@ -105,4 +113,28 @@ public abstract class MappedGuiListConfigGui< T, S extends ElementMappedToListGu protected abstract T createAndSaveNewEmptyGeneric(String name); + public static class LazyElement extends LazyValue { + + private final GuiItem parentItem; + private final LazyValue> lazyOpenConsumer; + public LazyElement(GuiItem parentItem, Supplier valueSupplier) { + super(valueSupplier); + this.parentItem = parentItem; + + this.lazyOpenConsumer = new LazyValue<>(() -> + GuiGlobalActions.openGuiAction(this.get().getMappedGui())) + ; + } + + public GuiItem getParentItem() { + return parentItem; + } + + @NotNull + public Consumer openAction(){ + return event -> lazyOpenConsumer.get().accept(event); + } + + } + } diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/SettingGuiListConfigGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/SettingGuiListConfigGui.java index 231cf67..f0e7cb0 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/SettingGuiListConfigGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/SettingGuiListConfigGui.java @@ -1,28 +1,34 @@ package xyz.alexcrea.cuanvil.gui.config.list; import com.github.stefvanschie.inventoryframework.gui.GuiItem; +import com.github.stefvanschie.inventoryframework.gui.type.util.Gui; import io.delilaheve.CustomAnvil; import org.bukkit.Material; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.jetbrains.annotations.NotNull; -import xyz.alexcrea.cuanvil.gui.config.settings.AbstractSettingGui; +import xyz.alexcrea.cuanvil.gui.config.MainConfigGui; +import xyz.alexcrea.cuanvil.gui.config.settings.SettingGui; import java.util.HashMap; import java.util.List; import java.util.function.Consumer; -public abstract class SettingGuiListConfigGui< T, S extends AbstractSettingGui.SettingGuiFactory> extends ElementListConfigGui< T >{ +public abstract class SettingGuiListConfigGui< T, S extends SettingGui.SettingGuiFactory> extends ElementListConfigGui< T >{ protected HashMap guiItemMap; protected HashMap factoryMap; - public SettingGuiListConfigGui(@NotNull String title) { - super(title); + protected SettingGuiListConfigGui(@NotNull String title, Gui parent) { + super(title, parent); this.guiItemMap = new HashMap<>(); this.factoryMap = new HashMap<>(); } + protected SettingGuiListConfigGui(@NotNull String title) { + this(title, MainConfigGui.getInstance()); + } + @Override protected GuiItem prepareCreateNewItem() { ItemStack createItem = new ItemStack(Material.PAPER); @@ -38,16 +44,16 @@ public abstract class SettingGuiListConfigGui< T, S extends AbstractSettingGui.S @Override public void updateValueForGeneric(T generic, boolean shouldUpdate) { - S factory = this.factoryMap.get(generic); - if(factory == null){ + if(!this.factoryMap.containsKey(generic)){ // Create new item & factory - factory = createFactory(generic); + S factory = createFactory(generic); GuiItem newItem = itemFromFactory(generic, factory); addToPage(newItem); this.guiItemMap.put(generic, newItem); this.factoryMap.put(generic, factory); }else{ + S factory = this.factoryMap.get(generic); // Update old item GuiItem oldItem = this.guiItemMap.get(generic); @@ -73,6 +79,7 @@ public abstract class SettingGuiListConfigGui< T, S extends AbstractSettingGui.S oldITem.setProperties(newItem.getProperties()); oldITem.setVisible(newItem.isVisible()); } + @Override protected GuiItem findGuiItemForRemoval(T generic) { return this.guiItemMap.get(generic); diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/UnitRepairElementListGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/UnitRepairElementListGui.java index 88b59f0..35f8ebb 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/UnitRepairElementListGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/UnitRepairElementListGui.java @@ -20,22 +20,20 @@ import xyz.alexcrea.cuanvil.util.CasedStringUtil; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.function.Consumer; public class UnitRepairElementListGui extends SettingGuiListConfigGui implements ElementMappedToListGui { - private final GuiItem parentItem; private final Material parentMaterial; private final UnitRepairConfigGui parentGui; private final String materialName; private boolean shouldWork = true; public UnitRepairElementListGui(@NotNull Material parentMaterial, - @NotNull UnitRepairConfigGui parentGui, - @NotNull GuiItem parentItem) { - super("\u00A7e" + CasedStringUtil.snakeToUpperSpacedCase(parentMaterial.name().toLowerCase()) + " \u00A7rUnit repair"); - this.parentItem = parentItem; + @NotNull UnitRepairConfigGui parentGui) { + super("§e" + CasedStringUtil.snakeToUpperSpacedCase(parentMaterial.name().toLowerCase()) + " §rUnit repair"); this.parentMaterial = parentMaterial; this.parentGui = parentGui; this.materialName = CasedStringUtil.snakeToUpperSpacedCase(parentMaterial.name().toLowerCase()); @@ -47,8 +45,8 @@ public class UnitRepairElementListGui extends SettingGuiListConfigGui getCreateItemLore() { return Arrays.asList( - "\u00A77Select a new item to be repairable.", - "\u00A77You will be asked the material to use." + "§7Select a new item to be repairable.", + "§7You will be asked the material to use." ); } @@ -63,26 +61,26 @@ public class UnitRepairElementListGui extends SettingGuiListConfigGui { ItemMeta meta = itemStack.getItemMeta(); Material type = itemStack.getType(); if(!(meta instanceof Damageable) || (type.getMaxDurability() <= 0)) { - player.sendMessage("\u00A7cThis item can't be damaged, so it can't be repaired."); + player.sendMessage("§cThis item can't be damaged, so it can't be repaired."); return; } if(type == this.parentMaterial){ - player.sendMessage("\u00A7cItem can't repair something of the same type."); + player.sendMessage("§cItem can't repair something of the same type."); return; } String materialName = type.name().toLowerCase(); // Add new material - ConfigHolder.UNIT_REPAIR_HOLDER.getConfig().set(parentMaterial.name().toLowerCase()+"."+materialName,0.25); + ConfigHolder.UNIT_REPAIR_HOLDER.getConfig().set(parentMaterial.name().toLowerCase() + "." + materialName,0.25); if (GuiSharedConstant.TEMPORARY_DO_SAVE_TO_DISK_EVERY_CHANGE) { ConfigHolder.UNIT_REPAIR_HOLDER.saveToDisk(GuiSharedConstant.TEMPORARY_DO_BACKUP_EVERY_SAVE); @@ -104,21 +102,21 @@ public class UnitRepairElementListGui extends SettingGuiListConfigGui getEveryDisplayableInstanceOfGeneric() { + protected Collection getEveryDisplayableInstanceOfGeneric() { ArrayList keys = new ArrayList<>(); if(!this.shouldWork){ return keys; @@ -164,11 +162,6 @@ public class UnitRepairElementListGui extends SettingGuiListConfigGui { + this.enchantSettingItem = new GuiItem(new ItemStack(Material.ENCHANTED_BOOK), event -> { event.setCancelled(true); EnchantSelectSettingGui enchantGui = new EnchantSelectSettingGui( - "\u00A7e" + CasedStringUtil.snakeToUpperSpacedCase(enchantConflict.toString()) + " \u00A75Enchantments", - this, this, 0); + "§e" + CasedStringUtil.snakeToUpperSpacedCase(enchantConflict.toString()) + "§5", + this, this); enchantGui.show(event.getWhoClicked()); }, CustomAnvil.instance); - this.groupSettingItem = new GuiItem(new ItemStack(Material.PAPER), (event) -> { + this.groupSettingItem = new GuiItem(new ItemStack(Material.PAPER), event -> { event.setCancelled(true); GroupSelectSettingGui enchantGui = new GroupSelectSettingGui( - "\u00A7e" + CasedStringUtil.snakeToUpperSpacedCase(this.enchantConflict.toString()) + " \u00A73Groups", + "§e" + CasedStringUtil.snakeToUpperSpacedCase(this.enchantConflict.toString()) + " §3Groups", this, this, 0); enchantGui.show(event.getWhoClicked()); }, CustomAnvil.instance); - this.minBeforeActiveSettingFactory = IntSettingsGui.intFactory( - "\u00A78Minimum enchantment count", + this.minBeforeActiveSettingFactory = new IntSettingsGui.IntSettingFactory( + "§8Minimum enchantment count", this, this.enchantConflict + ".maxEnchantmentBeforeConflict", ConfigHolder.CONFLICT_HOLDER, Arrays.asList( - "\u00A77Minimum enchantment count set to X mean only X enchantment can be put", - "\u00A77on an item before the conflict is active." + "§7Minimum enchantment count set to X mean only X enchantment can be put", + "§7on an item before the conflict is active." ), 0, 255, 0, 1 ); @@ -117,11 +116,8 @@ public class EnchantConflictSubSettingGui extends MappedToListSubSettingGui impl Supplier deleteSupplier = () -> { EnchantConflictManager manager = ConfigHolder.CONFLICT_HOLDER.getConflictManager(); - // Remove from manager - for (WrappedEnchantment enchantment : this.enchantConflict.getEnchants()) { - manager.removeConflictFromMap(enchantment, this.enchantConflict); - } - manager.conflictList.remove(this.enchantConflict); + // Remove from enchantment + manager.removeConflict(this.enchantConflict); // Remove from parent this.parent.removeGeneric(this.enchantConflict); @@ -130,7 +126,7 @@ public class EnchantConflictSubSettingGui extends MappedToListSubSettingGui impl cleanAndBeUnusable(); // Update config file storage - ConfigHolder.CONFLICT_HOLDER.getConfig().set(this.enchantConflict.toString(), null); + ConfigHolder.CONFLICT_HOLDER.delete(this.enchantConflict.toString()); // Save boolean success = true; @@ -141,8 +137,8 @@ public class EnchantConflictSubSettingGui extends MappedToListSubSettingGui impl return success; }; - return new ConfirmActionGui("\u00A7cDelete \u00A7e" + CasedStringUtil.snakeToUpperSpacedCase(this.enchantConflict.toString()) + "\u00A7c?", - "\u00A77Confirm that you want to delete this conflict.", + return new ConfirmActionGui("§cDelete §e" + CasedStringUtil.snakeToUpperSpacedCase(this.enchantConflict.toString()) + "§c?", + "§7Confirm that you want to delete this conflict.", this, this.parent, deleteSupplier ); } @@ -163,23 +159,23 @@ public class EnchantConflictSubSettingGui extends MappedToListSubSettingGui impl // Prepare enchantment lore ArrayList enchantLore = new ArrayList<>(); - enchantLore.add("\u00A77Allow you to select a list of \u00A75Enchantments \u00A77that this conflict should include"); - Set enchants = getSelectedEnchantments(); + enchantLore.add("§7Allow you to select a list of §5Enchantments §7that this conflict should include"); + Set enchants = getSelectedEnchantments(); if (enchants.isEmpty()) { - enchantLore.add("\u00A77There is no included enchantment for this conflict."); + enchantLore.add("§7There is no included enchantment for this conflict."); } else { - enchantLore.add("\u00A77List of included enchantment for this conflict:"); - Iterator enchantIterator = enchants.iterator(); + enchantLore.add("§7List of included enchantment for this conflict:"); + Iterator enchantIterator = enchants.iterator(); boolean greaterThanMax = enchants.size() > 5; int maxindex = (greaterThanMax ? 4 : enchants.size()); for (int i = 0; i < maxindex; i++) { // format string like "- Fire Protection" String formattedName = CasedStringUtil.snakeToUpperSpacedCase(enchantIterator.next().getKey().getKey()); - enchantLore.add("\u00A77- \u00A75" + formattedName); + enchantLore.add("§7- §5" + formattedName); } if (greaterThanMax) { - enchantLore.add("\u00A77And " + (enchants.size() - 4) + " more..."); + enchantLore.add("§7And " + (enchants.size() - 4) + " more..."); } } @@ -192,7 +188,7 @@ public class EnchantConflictSubSettingGui extends MappedToListSubSettingGui impl ItemMeta enchantMeta = enchantItem.getItemMeta(); assert enchantMeta != null; - enchantMeta.setDisplayName("\u00A7aSelect included \u00A75Enchantments \u00A7aSettings"); + enchantMeta.setDisplayName("§aSelect included §5Enchantments §aSettings"); enchantMeta.setLore(enchantLore); enchantItem.setItemMeta(enchantMeta); @@ -204,7 +200,7 @@ public class EnchantConflictSubSettingGui extends MappedToListSubSettingGui impl ItemMeta groupMeta = groupItem.getItemMeta(); assert groupMeta != null; - groupMeta.setDisplayName("\u00A7aSelect Excluded \u00A73Groups \u00A7aSettings"); + groupMeta.setDisplayName("§aSelect Excluded §3Groups §aSettings"); groupMeta.setLore(groupLore); groupItem.setItemMeta(groupMeta); @@ -243,12 +239,12 @@ public class EnchantConflictSubSettingGui extends MappedToListSubSettingGui impl // Select enchantment container methods @Override - public Set getSelectedEnchantments() { + public Set getSelectedEnchantments() { return this.enchantConflict.getEnchants(); } @Override - public boolean setSelectedEnchantments(Set enchantments) { + public boolean setSelectedEnchantments(Set enchantments) { if (!this.shouldWork) { CustomAnvil.instance.getLogger().info("Trying to save " + enchantConflict + " enchants but sub config is destroyed"); return false; @@ -260,8 +256,8 @@ public class EnchantConflictSubSettingGui extends MappedToListSubSettingGui impl // Save on file configuration String[] enchantKeys = new String[enchantments.size()]; int index = 0; - for (WrappedEnchantment enchantment : enchantments) { - enchantKeys[index++] = enchantment.getKey().getKey(); + for (CAEnchantment enchantment : enchantments) { + enchantKeys[index++] = enchantment.getKey().toString(); } ConfigHolder.CONFLICT_HOLDER.getConfig().set(enchantConflict + ".enchantments", enchantKeys); @@ -269,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 @@ -280,7 +277,7 @@ public class EnchantConflictSubSettingGui extends MappedToListSubSettingGui impl } @Override - public Set illegalEnchantments() { + public Set illegalEnchantments() { return Collections.emptySet(); } @@ -313,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 29d412b..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; @@ -37,10 +38,9 @@ public class GroupConfigSubSettingGui extends MappedToListSubSettingGui implemen public GroupConfigSubSettingGui( @NotNull GroupConfigGui parent, - @NotNull IncludeGroup group, - @NotNull GuiItem item) { - super(item, 3, - CasedStringUtil.snakeToUpperSpacedCase(group.getName()) + " Config"); + @NotNull IncludeGroup group) { + super(3, + "§e" + CasedStringUtil.snakeToUpperSpacedCase(group.getName()) + " §rConfig"); this.parent = parent; this.group = group; @@ -65,27 +65,38 @@ public class GroupConfigSubSettingGui extends MappedToListSubSettingGui implemen ItemStack deleteItem = new ItemStack(Material.RED_TERRACOTTA); ItemMeta deleteMeta = deleteItem.getItemMeta(); - deleteMeta.setDisplayName("\u00A74DELETE GROUP"); - deleteMeta.setLore(Collections.singletonList("\u00A7cCaution with this button !")); + deleteMeta.setDisplayName("§4DELETE GROUP"); + deleteMeta.setLore(Collections.singletonList("§cCaution with this button !")); deleteItem.setItemMeta(deleteMeta); 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); @@ -129,7 +140,7 @@ public class GroupConfigSubSettingGui extends MappedToListSubSettingGui implemen cleanAndBeUnusable(); // Update config file storage - ConfigHolder.CUSTOM_RECIPE_HOLDER.getConfig().set(this.group.getName(), null); + ConfigHolder.CUSTOM_RECIPE_HOLDER.delete(this.group.getName()); // Save boolean success = true; @@ -140,8 +151,8 @@ public class GroupConfigSubSettingGui extends MappedToListSubSettingGui implemen return success; }; - return new ConfirmActionGui("\u00A7cDelete \u00A7e" + CasedStringUtil.snakeToUpperSpacedCase(this.group.toString()) + "\u00A7c?", - "\u00A77Confirm that you want to delete this group.", + return new ConfirmActionGui("§cDelete §e" + CasedStringUtil.snakeToUpperSpacedCase(this.group.toString()) + "§c?", + "§7Confirm that you want to delete this group.", this, this.parent, deleteSupplier ); } @@ -151,8 +162,8 @@ public class GroupConfigSubSettingGui extends MappedToListSubSettingGui implemen if(usedLoc.isEmpty()){ return false; } - StringBuilder stb = new StringBuilder("\u00A7cCan't delete group " +this.group.getName()+ - "\n\u00A7eUsed by:"); + StringBuilder stb = new StringBuilder("§cCan't delete group " +this.group.getName()+ + "\n§eUsed by:"); int maxIndex = usedLoc.size(); int nbMore = 0; if(maxIndex > 10){ @@ -160,10 +171,10 @@ public class GroupConfigSubSettingGui extends MappedToListSubSettingGui implemen maxIndex = 9; } for (int i = 0; i < maxIndex; i++) { - stb.append("\n\u00A7r-\u00A7e ").append(usedLoc.get(i)); + stb.append("\n§r-§e ").append(usedLoc.get(i)); } if(nbMore > 0){ - stb.append("\u00A7cAnd ").append(nbMore).append(" More..."); + stb.append("§cAnd ").append(nbMore).append(" More..."); } player.sendMessage(stb.toString()); @@ -214,7 +225,7 @@ public class GroupConfigSubSettingGui extends MappedToListSubSettingGui implemen ItemStack matSelectItem = this.materialSelection.getItem(); ItemMeta matSelectMeta = matSelectItem.getItemMeta(); - matSelectMeta.setDisplayName("\u00A7aSelect included \u00A7eMaterials \u00A7aSettings"); + matSelectMeta.setDisplayName("§aSelect included §eMaterials §aSettings"); matSelectMeta.setLore(matLore); matSelectMeta.addItemFlags(ItemFlag.values()); @@ -226,7 +237,7 @@ public class GroupConfigSubSettingGui extends MappedToListSubSettingGui implemen ItemStack groupSelectItem = this.groupSelection.getItem(); ItemMeta groupSelectMeta = groupSelectItem.getItemMeta(); - groupSelectMeta.setDisplayName("\u00A7aSelect included \u00A73Groups \u00A7aSettings"); + groupSelectMeta.setDisplayName("§aSelect included §3Groups §aSettings"); groupSelectMeta.setLore(groupLore); groupSelectItem.setItemMeta(groupSelectMeta); @@ -311,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); @@ -343,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){ @@ -371,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/list/elements/MappedToListSubSettingGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/MappedToListSubSettingGui.java index 68273bc..1d86781 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/MappedToListSubSettingGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/MappedToListSubSettingGui.java @@ -1,25 +1,18 @@ package xyz.alexcrea.cuanvil.gui.config.list.elements; import com.github.stefvanschie.inventoryframework.gui.GuiItem; +import com.github.stefvanschie.inventoryframework.gui.type.ChestGui; import com.github.stefvanschie.inventoryframework.gui.type.util.Gui; import io.delilaheve.CustomAnvil; import org.jetbrains.annotations.NotNull; import xyz.alexcrea.cuanvil.gui.ValueUpdatableGui; -public abstract class MappedToListSubSettingGui extends ValueUpdatableGui implements ElementMappedToListGui { +public abstract class MappedToListSubSettingGui extends ChestGui implements ValueUpdatableGui, ElementMappedToListGui { - private final GuiItem item; - public MappedToListSubSettingGui( - GuiItem item, + protected MappedToListSubSettingGui( int rows, @NotNull String title) { super(rows, title, CustomAnvil.instance); - this.item = item; - } - - @Override - public GuiItem getParentItemForThisGui() { - return item; } @Override @@ -27,5 +20,9 @@ public abstract class MappedToListSubSettingGui extends ValueUpdatableGui implem return this; } + @Override + public Gui getConnectedGui() { + return this; + } } diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/AbstractSettingGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/AbstractSettingGui.java index d952f0c..b07e7c1 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/AbstractSettingGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/AbstractSettingGui.java @@ -15,9 +15,9 @@ import xyz.alexcrea.cuanvil.gui.util.GuiGlobalItems; /** * An instance gui used to edit a setting. */ -public abstract class AbstractSettingGui extends ChestGui { +public abstract class AbstractSettingGui extends ChestGui implements SettingGui { - protected final static String CLICK_LORE = "\u00A77Click Here to change the value"; + public static final String CLICK_LORE = "§7Click Here to change the value"; private PatternPane pane; @@ -28,7 +28,7 @@ public abstract class AbstractSettingGui extends ChestGui { * @param title Title of this gui. * @param parent Parent gui to go back when completed. */ - public AbstractSettingGui(int rows, @NotNull TextHolder title, ValueUpdatableGui parent) { + protected AbstractSettingGui(int rows, @NotNull TextHolder title, ValueUpdatableGui parent) { super(rows, title, CustomAnvil.instance); initBase(parent); } @@ -40,12 +40,11 @@ public abstract class AbstractSettingGui extends ChestGui { * @param title Title of this gui. * @param parent Parent gui to go back when completed. */ - public AbstractSettingGui(int rows, @NotNull String title, ValueUpdatableGui parent) { + protected AbstractSettingGui(int rows, @NotNull String title, ValueUpdatableGui parent) { this(rows, StringHolder.of(title), parent); } protected GuiItem saveItem; - protected GuiItem noChangeItem; /** * Initialise and prepare value for this gui. @@ -57,19 +56,18 @@ public abstract class AbstractSettingGui extends ChestGui { pane = new PatternPane(0, 0, pattern.getLength(), pattern.getHeight(), pattern); addPane(pane); - GuiGlobalItems.addBackItem(pane, parent); + GuiGlobalItems.addBackItem(pane, parent.getConnectedGui()); GuiGlobalItems.addBackgroundItem(pane); saveItem = GuiGlobalItems.saveItem(this, parent); - noChangeItem = GuiGlobalItems.noChangeItem(); - pane.bindItem('S', noChangeItem); + pane.bindItem('S', GuiGlobalItems.noChangeItem()); } @Override public void update() { - pane.bindItem('S', hadChange() ? saveItem : noChangeItem); + pane.bindItem('S', hadChange() ? saveItem : GuiGlobalItems.noChangeItem()); super.update(); } @@ -95,27 +93,13 @@ public abstract class AbstractSettingGui extends ChestGui { */ protected abstract Pattern getGuiPattern(); - /** - * Called when the associated setting need to be saved. - * - * @return true if the save was successful. false otherwise - */ - public abstract boolean onSave(); - - /** - * If this function return true - * the gui assume the associated setting can be saved. - * - * @return true if there is a change to the setting. false otherwise - */ - public abstract boolean hadChange(); /** * Most of the time a setting gui will be called from a global gui. *

    * It is better to keep a factory that hold setting data than find what parameters to use every time. */ - public abstract static class SettingGuiFactory { + public abstract static class SettingGuiFactory implements SettingGui.SettingGuiFactory { @NotNull protected String configPath; @NotNull @@ -148,11 +132,5 @@ public abstract class AbstractSettingGui extends ChestGui { return config; } - /** - * Create a gui using setting parameters and current setting value. - * - * @return A new instance of the implemented setting gui. - */ - public abstract AbstractSettingGui create(); } } diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/BoolSettingsGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/BoolSettingsGui.java index ca34382..1d5e73d 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/BoolSettingsGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/BoolSettingsGui.java @@ -1,6 +1,7 @@ package xyz.alexcrea.cuanvil.gui.config.settings; import com.github.stefvanschie.inventoryframework.gui.GuiItem; +import com.github.stefvanschie.inventoryframework.gui.type.util.Gui; import com.github.stefvanschie.inventoryframework.pane.PatternPane; import com.github.stefvanschie.inventoryframework.pane.util.Pattern; import io.delilaheve.CustomAnvil; @@ -65,9 +66,9 @@ public class BoolSettingsGui extends AbstractSettingGui { // Prepare default Value text String defaultValueLore; if(holder.defaultVal){ - defaultValueLore = "\u00A7aYes \u00A77Is the default value"; + defaultValueLore = "§aYes §7Is the default value"; }else{ - defaultValueLore = "\u00A7cNo \u00A77Is the default value"; + defaultValueLore = "§cNo §7Is the default value"; } // Create reset to default item @@ -75,7 +76,7 @@ public class BoolSettingsGui extends AbstractSettingGui { ItemMeta meta = item.getItemMeta(); assert meta != null; - meta.setDisplayName("\u00A7eReset to default value"); + meta.setDisplayName("§eReset to default value"); meta.setLore(Collections.singletonList(defaultValueLore)); item.setItemMeta(meta); returnToDefault = new GuiItem(item, event -> { @@ -96,10 +97,10 @@ public class BoolSettingsGui extends AbstractSettingGui { String displayedName; Material displayedMat; if (now) { - displayedName = "\u00A7aYes"; + displayedName = "§aYes"; displayedMat = Material.GREEN_TERRACOTTA; } else { - displayedName = "\u00A7cNo"; + displayedName = "§cNo"; displayedMat = Material.RED_TERRACOTTA; } @@ -160,28 +161,6 @@ public class BoolSettingsGui extends AbstractSettingGui { return now != before; } - /** - * Create a bool setting factory from setting's parameters. - * - * @param title The title of the gui. - * @param parent Parent gui to go back when completed. - * @param config Configuration holder of this setting. - * @param configPath Configuration path of this setting. - * @param defaultVal Default value if not found on the config. - * @param displayLore Gui display item lore. - * @return A factory for a boolean setting gui. - */ - public static BoolSettingFactory boolFactory(@NotNull String title, @NotNull ValueUpdatableGui parent, - @NotNull ConfigHolder config, - @NotNull String configPath, boolean defaultVal, - String... displayLore) { - return new BoolSettingFactory( - title, parent, - config, - configPath, defaultVal, - displayLore); - } - /** * A factory for a boolean setting gui that hold setting's information. */ @@ -205,7 +184,7 @@ public class BoolSettingsGui extends AbstractSettingGui { * @param defaultVal Default value if not found on the config. * @param displayLore Gui display item lore. */ - protected BoolSettingFactory( + public BoolSettingFactory( @NotNull String title, @NotNull ValueUpdatableGui parent, @NotNull ConfigHolder config, @NotNull String configPath, boolean defaultVal, String... displayLore) { @@ -233,7 +212,7 @@ public class BoolSettingsGui extends AbstractSettingGui { } @Override - public AbstractSettingGui create() { + public Gui create() { // Get current value or default boolean now = getConfiguredValue(); // create new gui @@ -253,14 +232,14 @@ public class BoolSettingsGui extends AbstractSettingGui { boolean value = getConfiguredValue(); Material itemMat; - StringBuilder itemName = new StringBuilder("\u00A7e"); + StringBuilder itemName = new StringBuilder("§e"); String finalValue; if (value) { itemMat = Material.GREEN_TERRACOTTA; - finalValue = "\u00A7aYes"; + finalValue = "§aYes"; } else { itemMat = Material.RED_TERRACOTTA; - finalValue = "\u00A7cNo"; + finalValue = "§cNo"; } itemName.append(name); diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/DoubleSettingGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/DoubleSettingGui.java index 1b99c59..c0dcff6 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/DoubleSettingGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/DoubleSettingGui.java @@ -1,6 +1,7 @@ package xyz.alexcrea.cuanvil.gui.config.settings; import com.github.stefvanschie.inventoryframework.gui.GuiItem; +import com.github.stefvanschie.inventoryframework.gui.type.util.Gui; import com.github.stefvanschie.inventoryframework.pane.PatternPane; import com.github.stefvanschie.inventoryframework.pane.util.Pattern; import io.delilaheve.CustomAnvil; @@ -67,9 +68,9 @@ public class DoubleSettingGui extends AbstractSettingGui { ItemMeta meta = DELETE_ITEM_STACK.getItemMeta(); assert meta != null; - meta.setDisplayName("\u00A7cDisable item being repaired ?"); - meta.setLore(Arrays.asList("\u00A77Confirm disabling unit repair for this item..", - "\u00A74Cation: This action can't be canceled.")); + meta.setDisplayName("§cDisable item being repaired ?"); + meta.setLore(Arrays.asList("§7Confirm disabling unit repair for this item..", + "§4Cation: This action can't be canceled.")); DELETE_ITEM_STACK.setItemMeta(meta); } @@ -120,8 +121,8 @@ public class DoubleSettingGui extends AbstractSettingGui { ItemMeta meta = item.getItemMeta(); assert meta != null; - meta.setDisplayName("\u00A7eReset to default value"); - meta.setLore(Collections.singletonList("\u00A77Default value is \u00A7e" + displayValue(holder.defaultVal))); + meta.setDisplayName("§eReset to default value"); + meta.setLore(Collections.singletonList("§7Default value is §e" + displayValue(holder.defaultVal))); item.setItemMeta(meta); returnToDefault = new GuiItem(item, event -> { event.setCancelled(true); @@ -143,7 +144,7 @@ public class DoubleSettingGui extends AbstractSettingGui { if (now.compareTo(holder.min) > 0) { BigDecimal planned = holder.min.max(now.subtract(step)); - minusItem = getSetValueItem(Material.RED_TERRACOTTA, planned, "\u00A7c-"); + minusItem = getSetValueItem(Material.RED_TERRACOTTA, planned, "§c-"); } else { minusItem = GuiGlobalItems.backgroundItem(Material.BARRIER); } @@ -154,7 +155,7 @@ public class DoubleSettingGui extends AbstractSettingGui { if (now.compareTo(holder.max) < 0) { BigDecimal planned = holder.max.min(now.add(step)); - plusItem = getSetValueItem(Material.GREEN_TERRACOTTA, planned, "\u00A7a+"); + plusItem = getSetValueItem(Material.GREEN_TERRACOTTA, planned, "§a+"); } else { plusItem = GuiGlobalItems.backgroundItem(Material.BARRIER); } @@ -165,7 +166,7 @@ public class DoubleSettingGui extends AbstractSettingGui { ItemMeta resultMeta = resultPaper.getItemMeta(); assert resultMeta != null; - resultMeta.setDisplayName("\u00A7fValue: \u00A7e" + displayValue(now)); + resultMeta.setDisplayName("§fValue: §e" + displayValue(now)); resultPaper.setItemMeta(resultMeta); GuiItem resultItem = new GuiItem(resultPaper, GuiGlobalActions.stayInPlace, CustomAnvil.instance); @@ -196,8 +197,8 @@ public class DoubleSettingGui extends AbstractSettingGui { ItemMeta meta = item.getItemMeta(); assert meta != null; - meta.setDisplayName("\u00A7e" + displayValue(now) + " \u00A7f-> \u00A7e" + displayValue(planned) - + " \u00A7r(" + numberPrefix + (displayValue(planned.subtract(now).abs()) + "\u00A7r)")); + meta.setDisplayName("§e" + displayValue(now) + " §f-> §e" + displayValue(planned) + + " §r(" + numberPrefix + (displayValue(planned.subtract(now).abs()) + "§r)")); meta.setLore(setLoreItem); item.setItemMeta(meta); @@ -270,21 +271,21 @@ public class DoubleSettingGui extends AbstractSettingGui { // Get material properties Material stepMat; - StringBuilder stepName = new StringBuilder("\u00A7"); + StringBuilder stepName = new StringBuilder("§"); List stepLore; Consumer clickEvent; if (stepValue.compareTo(step) == 0) { stepMat = Material.GREEN_STAINED_GLASS_PANE; stepName.append('a'); - stepLore = Collections.singletonList("\u00A77Value is changing by " + displayValue(stepValue)); + stepLore = Collections.singletonList("§7Value is changing by " + displayValue(stepValue)); clickEvent = GuiGlobalActions.stayInPlace; } else { stepMat = Material.RED_STAINED_GLASS_PANE; stepName.append('c'); - stepLore = Collections.singletonList("\u00A77Click here to change the value by " + displayValue(stepValue)); + stepLore = Collections.singletonList("§7Click here to change the value by " + displayValue(stepValue)); clickEvent = updateStepValue(stepValue); } - stepName.append("Step of \u00A7e").append(displayValue(stepValue)); + stepName.append("Step of §e").append(displayValue(stepValue)); // Create item stack then gui item ItemStack item = new ItemStack(stepMat); @@ -315,7 +316,11 @@ public class DoubleSettingGui extends AbstractSettingGui { @Override public boolean onSave() { if(isNull()){ - this.holder.config.getConfig().set(this.holder.configPath, null); + if(this.holder.config instanceof ConfigHolder.DeletableResource deletableResource){ + deletableResource.delete(this.holder.configPath); + }else{ + this.holder.config.getConfig().set(this.holder.configPath, null); + } }else{ this.holder.config.getConfig().set(this.holder.configPath, now.doubleValue()); } @@ -347,42 +352,6 @@ public class DoubleSettingGui extends AbstractSettingGui { return value.toString(); } - /** - * Create a double setting factory from setting's parameters. - * - * @param title The title of the gui. - * @param parent Parent gui to go back when completed. - * @param config Configuration holder of this setting. - * @param configPath Configuration path of this setting. - * @param displayLore Gui display item lore. - * @param scale The scale of the decimal. - * @param asPercentage If we should display the value as a %. - * @param nullOnZero Set the value as null (deleting it) when equal to 0 - * @param min Minimum value of this setting. - * @param max Maximum value of this setting. - * @param defaultVal Default value if not found on the config. - * @param steps List of step the value can increment/decrement. - * List's size should be between 1 (included) and 5 (included). - * it is visually preferable to have an odd number of step. - * If step only contain 1 value, no step item should be displayed. - * @return A factory for a double setting gui. - */ - @NotNull - public static DoubleSettingFactory doubleFactory(@NotNull String title, @NotNull ValueUpdatableGui parent, - @NotNull ConfigHolder config, - @NotNull String configPath, - @Nullable List displayLore, - int scale, boolean asPercentage, boolean nullOnZero, - double min, double max, double defaultVal, double... steps) { - return new DoubleSettingFactory( - title, parent, - config, - configPath, - displayLore, - scale, asPercentage, nullOnZero, - min, max, defaultVal, steps); - } - /** * A factory for a double setting gui that hold setting's information. */ @@ -422,7 +391,7 @@ public class DoubleSettingGui extends AbstractSettingGui { * it is visually preferable to have an odd number of step. * If step only contain 1 value, no step item should be displayed. */ - protected DoubleSettingFactory( + public DoubleSettingFactory( @NotNull String title, @NotNull ValueUpdatableGui parent, @NotNull ConfigHolder config, @NotNull String configPath, @@ -471,7 +440,7 @@ public class DoubleSettingGui extends AbstractSettingGui { } @Override - public AbstractSettingGui create() { + public Gui create() { // Get value or default BigDecimal now = getConfiguredValue(); // create new gui @@ -482,10 +451,10 @@ public class DoubleSettingGui extends AbstractSettingGui { public GuiItem getItem(Material itemMat, String name){ // Get item properties BigDecimal value = getConfiguredValue(); - StringBuilder itemName = new StringBuilder("\u00A7a").append(name); + StringBuilder itemName = new StringBuilder("§a").append(name); return GuiGlobalItems.createGuiItemFromProperties(this, itemMat, itemName, - "\u00A7e" + displayValue(value, this.asPercentage), + "§e" + displayValue(value, this.asPercentage), this.displayLore, true); } diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/EnchantCostSettingsGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/EnchantCostSettingsGui.java index 85a1806..3bd923b 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/EnchantCostSettingsGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/EnchantCostSettingsGui.java @@ -1,9 +1,11 @@ package xyz.alexcrea.cuanvil.gui.config.settings; import com.github.stefvanschie.inventoryframework.gui.GuiItem; +import com.github.stefvanschie.inventoryframework.gui.type.util.Gui; import com.github.stefvanschie.inventoryframework.pane.PatternPane; import com.github.stefvanschie.inventoryframework.pane.util.Pattern; import io.delilaheve.CustomAnvil; +import io.delilaheve.util.ConfigOptions; import org.bukkit.Material; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.inventory.ItemFlag; @@ -12,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.enchant.CAEnchantment; import xyz.alexcrea.cuanvil.gui.ValueUpdatableGui; import xyz.alexcrea.cuanvil.gui.util.GuiGlobalActions; import xyz.alexcrea.cuanvil.gui.util.GuiGlobalItems; @@ -82,10 +85,10 @@ public class EnchantCostSettingsGui extends IntSettingsGui { ItemMeta bookMeta = bookItemstack.getItemMeta(); assert bookMeta != null; - bookMeta.setDisplayName("\u00A7aCost of an Enchantment by Book"); + bookMeta.setDisplayName("§aCost of an Enchantment by Book"); bookMeta.setLore(Arrays.asList( - "\u00A77Cost per result item level of an sacrifice enchantment", - "\u00A77Only apply if sacrificed item \u00A7cis \u00A77a book")); + "§7Cost per result item level of an sacrifice enchantment", + "§7Only apply if sacrificed item §cis §7a book")); bookItemstack.setItemMeta(bookMeta); // sword display @@ -94,10 +97,10 @@ public class EnchantCostSettingsGui extends IntSettingsGui { assert swordMeta != null; swordMeta.addItemFlags(ItemFlag.values()); - swordMeta.setDisplayName("\u00A7aCost of an Enchantment by Item"); + swordMeta.setDisplayName("§aCost of an Enchantment by Item"); swordMeta.setLore(Arrays.asList( - "\u00A77Cost per result item level of an sacrifice enchantment", - "\u00A77Only apply if sacrificed item \u00A7cis not \u00A77a book")); + "§7Cost per result item level of an sacrifice enchantment", + "§7Only apply if sacrificed item §cis not §7a book")); swordItemstack.setItemMeta(swordMeta); pane.bindItem('1', GuiGlobalItems.backgroundItem(Material.BLACK_STAINED_GLASS_PANE)); @@ -114,10 +117,10 @@ public class EnchantCostSettingsGui extends IntSettingsGui { // assume holder is an instance of EnchantCostSettingFactory EnchantCostSettingFactory holder = (EnchantCostSettingFactory) this.holder; - meta.setDisplayName("\u00A7eReset to default value"); + meta.setDisplayName("§eReset to default value"); meta.setLore(Arrays.asList( - "\u00A77Default item value is: \u00A7e" + holder.defaultVal, - "\u00A77Default book value is: \u00A7e" + holder.defaultBookVal)); + "§7Default item value is: §e" + holder.defaultVal, + "§7Default book value is: §e" + holder.defaultBookVal)); item.setItemMeta(meta); returnToDefault = new GuiItem(item, event -> { event.setCancelled(true); @@ -146,7 +149,7 @@ public class EnchantCostSettingsGui extends IntSettingsGui { ItemMeta meta = item.getItemMeta(); assert meta != null; - meta.setDisplayName("\u00A7e" + nowBook + " \u00A7f-> \u00A7e" + planned + " \u00A7r(\u00A7c-" + (nowBook - planned) + "\u00A7r)"); + meta.setDisplayName("§e" + nowBook + " §f-> §e" + planned + " §r(§c-" + (nowBook - planned) + "§r)"); meta.setLore(Collections.singletonList(AbstractSettingGui.CLICK_LORE)); item.setItemMeta(meta); @@ -164,7 +167,7 @@ public class EnchantCostSettingsGui extends IntSettingsGui { ItemMeta meta = item.getItemMeta(); assert meta != null; - meta.setDisplayName("\u00A7e" + nowBook + " \u00A7f-> \u00A7e" + planned + " \u00A7r(\u00A7a+" + (planned - nowBook) + "\u00A7r)"); + meta.setDisplayName("§e" + nowBook + " §f-> §e" + planned + " §r(§a+" + (planned - nowBook) + "§r)"); meta.setLore(Collections.singletonList(AbstractSettingGui.CLICK_LORE)); item.setItemMeta(meta); @@ -179,7 +182,7 @@ public class EnchantCostSettingsGui extends IntSettingsGui { ItemMeta nowMeta = nowPaper.getItemMeta(); assert nowMeta != null; - nowMeta.setDisplayName("\u00A7fValue: \u00A7e" + nowBook); + nowMeta.setDisplayName("§fValue: §e" + nowBook); if(!holder.displayLore.isEmpty()){ nowMeta.setLore(holder.displayLore); } @@ -236,73 +239,45 @@ public class EnchantCostSettingsGui extends IntSettingsGui { return super.hadChange() || nowBook != beforeBook; } - /** - * Create an int setting factory from setting's parameters. - * - * @param title The title of the gui. - * @param parent Parent gui to go back when completed. - * @param config Configuration holder of this setting. - * @param configPath Configuration path of this setting. - * @param displayLore Gui display item lore. - * @param min Minimum value of this setting. - * @param max Maximum value of this setting. - * @param defaultItemVal Default item value if not found on the config. - * @param defaultBookVal Default book value if not found on the config. - * @param steps List of step the value can increment/decrement. - * List's size should be between 1 (included) and 3 (included). - * it is visually preferable to have an odd number of step. - * If step only contain 1 value, no step item should be displayed. - * @return A factory for an enchant cost setting gui. - */ - public static EnchantCostSettingFactory enchantCostFactory( - @NotNull String title, @NotNull ValueUpdatableGui parent, - @NotNull ConfigHolder config, @NotNull String configPath, - @Nullable List displayLore, - int min, int max, int defaultItemVal, int defaultBookVal, - int... steps) { - return new EnchantCostSettingFactory( - title, parent, - configPath, config, - displayLore, - min, max, defaultItemVal, defaultBookVal, steps); - } - /** * A factory for an enchantment cost setting gui that hold setting's information. */ public static class EnchantCostSettingFactory extends IntSettingsGui.IntSettingFactory { int defaultBookVal; + @NotNull CAEnchantment enchantment; /** * Constructor for an enchantment cost setting gui factory. * - * @param title The title of the gui. - * @param parent Parent gui to go back when completed. - * @param configPath Configuration path of this setting. - * @param config Configuration holder of this setting. - * @param displayLore Gui display item lore. - * @param min Minimum value of this setting. - * @param max Maximum value of this setting. - * @param defaultItemVal Default item value if not found on the config. - * @param defaultBookVal Default book value if not found on the config. - * @param steps List of step the value can increment/decrement. - * List's size should be between 1 (included) and 3 (included). - * it is visually preferable to have an odd number of step. - * If step only contain 1 value, no step item should be displayed. + * @param title The title of the gui. + * @param parent Parent gui to go back when completed. + * @param configPath Configuration path of this setting. + * @param config Configuration holder of this setting. + * @param displayLore Gui display item lore. + * @param min Minimum value of this setting. + * @param max Maximum value of this setting. + * @param enchantment Enchantment to change the cost to + * @param steps List of step the value can increment/decrement. + * List's size should be between 1 (included) and 3 (included). + * it is visually preferable to have an odd number of step. + * If step only contain 1 value, no step item should be displayed. */ - protected EnchantCostSettingFactory( + public EnchantCostSettingFactory( @NotNull String title, ValueUpdatableGui parent, @NotNull String configPath, @NotNull ConfigHolder config, @Nullable List displayLore, - int min, int max, int defaultItemVal, int defaultBookVal, - int... steps) { + @NotNull CAEnchantment enchantment, + int min, int max, int... steps) { super(title, parent, configPath, config, displayLore, - min, max, defaultItemVal, steps); - this.defaultBookVal = defaultBookVal; + min, max, enchantment.defaultRarity().getItemValue(), + steps); + + this.defaultBookVal = enchantment.defaultRarity().getBookValue(); + this.enchantment = enchantment; } /** @@ -310,18 +285,18 @@ public class EnchantCostSettingsGui extends IntSettingsGui { */ @Override public int getConfiguredValue() { - return this.config.getConfig().getInt(this.configPath + ITEM_PATH, this.defaultVal); + return ConfigOptions.INSTANCE.enchantmentValue(enchantment, false); } /** * @return The configured value for the enchant setting book value. */ public int getConfiguredBookValue() { - return this.config.getConfig().getInt(this.configPath + BOOK_PATH, this.defaultBookVal); + return ConfigOptions.INSTANCE.enchantmentValue(enchantment, true); } @Override - public AbstractSettingGui create() { + public Gui create() { // Get value or default int nowItem = getConfiguredValue(); // create new gui diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/EnchantSelectSettingGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/EnchantSelectSettingGui.java index 6eeef37..176da55 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/EnchantSelectSettingGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/EnchantSelectSettingGui.java @@ -1,9 +1,7 @@ package xyz.alexcrea.cuanvil.gui.config.settings; import com.github.stefvanschie.inventoryframework.gui.GuiItem; -import com.github.stefvanschie.inventoryframework.pane.Orientable; -import com.github.stefvanschie.inventoryframework.pane.OutlinePane; -import com.github.stefvanschie.inventoryframework.pane.Pane; +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; @@ -13,71 +11,81 @@ import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.jetbrains.annotations.NotNull; -import xyz.alexcrea.cuanvil.enchant.WrappedEnchantment; +import xyz.alexcrea.cuanvil.config.ConfigHolder; +import xyz.alexcrea.cuanvil.enchant.CAEnchantment; +import xyz.alexcrea.cuanvil.enchant.CAEnchantmentRegistry; import xyz.alexcrea.cuanvil.gui.ValueUpdatableGui; +import xyz.alexcrea.cuanvil.gui.config.MainConfigGui; import xyz.alexcrea.cuanvil.gui.config.SelectEnchantmentContainer; +import xyz.alexcrea.cuanvil.gui.config.list.SettingGuiListConfigGui; +import xyz.alexcrea.cuanvil.gui.util.GuiGlobalItems; import xyz.alexcrea.cuanvil.gui.util.GuiSharedConstant; import xyz.alexcrea.cuanvil.util.CasedStringUtil; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; import java.util.function.Consumer; +import java.util.stream.Stream; -public class EnchantSelectSettingGui extends AbstractSettingGui { +public class EnchantSelectSettingGui extends SettingGuiListConfigGui implements SettingGui { - SelectEnchantmentContainer enchantContainer; - int page; + private final SelectEnchantmentContainer enchantContainer; - Set selectedEnchant; + private final Set selectedEnchant; + private final GuiItem saveItem; - public EnchantSelectSettingGui(@NotNull String title, ValueUpdatableGui parent, SelectEnchantmentContainer enchantContainer, int page) { - super(6, title, parent); + private boolean displayUnselected; + + public EnchantSelectSettingGui(@NotNull String title, ValueUpdatableGui parent, SelectEnchantmentContainer enchantContainer) { + super(title, parent instanceof Gui parentGui ? parentGui : MainConfigGui.getInstance()) ; this.enchantContainer = enchantContainer; - // Not used and not planned rn - this.page = page; this.selectedEnchant = new HashSet<>(enchantContainer.getSelectedEnchantments()); - // Add secondary background item - this.getPane().bindItem('1', GuiSharedConstant.SECONDARY_BACKGROUND_ITEM); + this.saveItem = GuiGlobalItems.saveItem(this, parent); + this.backgroundPane.bindItem('S', GuiGlobalItems.noChangeItem()); - initGroups(); + this.displayUnselected = true; + this.backgroundPane.bindItem('b', createDisplayUnusedItem()); + + init(); } @Override - protected Pattern getGuiPattern() { + protected Pattern getBackgroundPattern() { return new Pattern( - GuiSharedConstant.EMPTY_GUI_FULL_LINE, - GuiSharedConstant.EMPTY_GUI_FULL_LINE, - GuiSharedConstant.EMPTY_GUI_FULL_LINE, - GuiSharedConstant.EMPTY_GUI_FULL_LINE, - GuiSharedConstant.EMPTY_GUI_FULL_LINE, - "B1111111S" + GuiSharedConstant.UPPER_FILLER_FULL_PLANE, + GuiSharedConstant.EMPTY_FILLER_FULL_LINE, + GuiSharedConstant.EMPTY_FILLER_FULL_LINE, + GuiSharedConstant.EMPTY_FILLER_FULL_LINE, + GuiSharedConstant.EMPTY_FILLER_FULL_LINE, + "B11LbR11S" ); } - protected void initGroups() { - // Add enchantment gui item - OutlinePane filledEnchant = new OutlinePane(0, 0, 9, 5); - filledEnchant.setPriority(Pane.Priority.HIGH); - filledEnchant.align(OutlinePane.Alignment.BEGIN); - filledEnchant.setOrientation(Orientable.Orientation.HORIZONTAL); - - Set illegalEnchant = this.enchantContainer.illegalEnchantments(); - for (WrappedEnchantment enchant : GuiSharedConstant.SORTED_ENCHANTMENT_LIST) { - if (illegalEnchant.contains(enchant)) { - return; - } - filledEnchant.addItem(getGuiItemFromEnchant(enchant)); + @Override + protected Collection getEveryDisplayableInstanceOfGeneric() { + Stream toDisplayStream; + if(this.displayUnselected){ + toDisplayStream = CAEnchantmentRegistry.getInstance().getNameSortedEnchantments().stream(); + }else{ + toDisplayStream = this.selectedEnchant.stream().sorted(Comparator.comparing(CAEnchantment::getName)); } + Set illegalEnchantments = this.enchantContainer.illegalEnchantments(); - addPane(filledEnchant); + return toDisplayStream + .filter(enchantment -> !illegalEnchantments.contains(enchantment)) + .toList(); } - private GuiItem getGuiItemFromEnchant(WrappedEnchantment enchantment) { + @Override + public void update() { + this.backgroundPane.bindItem('S', hadChange() ? saveItem : GuiGlobalItems.noChangeItem()); + super.update(); + } + + @Override + protected GuiItem itemFromFactory(CAEnchantment enchantment, DummyFactory factory) { boolean isIn = this.selectedEnchant.contains(enchantment); Material usedMaterial; @@ -95,22 +103,43 @@ public class EnchantSelectSettingGui extends AbstractSettingGui { return guiItem; } + private GuiItem createDisplayUnusedItem() { + ItemStack item = new ItemStack(this.displayUnselected ? Material.BOOK : Material.ENCHANTED_BOOK); + ItemMeta meta = item.getItemMeta(); + assert meta != null; - private static final List TRUE_LORE = Collections.singletonList("\u00A77Value: \u00A7aSelected"); - private static final List FALSE_LORE = Collections.singletonList("\u00A77Value: \u00A7cNot Selected"); + meta.setDisplayName((this.displayUnselected ? "§aEverything displayed" : "§eOnly selected displayed")); + meta.setLore(Collections.singletonList( + "§7Click here to see " + + (this.displayUnselected ? "only selected" : "every") + + " enchantments")); + + item.setItemMeta(meta); + + return new GuiItem(item, clickEvent -> { + clickEvent.setCancelled(true); + this.displayUnselected = !this.displayUnselected; + + this.backgroundPane.bindItem('b', createDisplayUnusedItem()); + reloadValues(); + }, CustomAnvil.instance); + } + + private static final List TRUE_LORE = Collections.singletonList("§7Value: §aSelected"); + private static final List FALSE_LORE = Collections.singletonList("§7Value: §cNot Selected"); public void setEnchantItemMeta(ItemStack item, String name, boolean isIn) { ItemMeta meta = item.getItemMeta(); if (meta == null) { CustomAnvil.instance.getLogger().warning("Could not create item for enchantment: " + name + ":\n" + - "Item do not gave item meta: " + item + ". Using placeholder instead"); + "Item do not gave item meta: " + item + ". Using a placeholder item instead"); item.setType(Material.PAPER); meta = item.getItemMeta(); assert meta != null; } - meta.setDisplayName("\u00A7" + (isIn ? 'a' : 'c') + CasedStringUtil.snakeToUpperSpacedCase(name)); + meta.setDisplayName("§" + (isIn ? 'a' : 'c') + CasedStringUtil.snakeToUpperSpacedCase(name)); if (isIn) { meta.addEnchant(Enchantment.DAMAGE_UNDEAD, 1, true); meta.setLore(TRUE_LORE); @@ -123,7 +152,7 @@ public class EnchantSelectSettingGui extends AbstractSettingGui { item.setItemMeta(meta); } - private Consumer getEnchantItemConsumer(WrappedEnchantment enchant, GuiItem guiItem) { + private Consumer getEnchantItemConsumer(CAEnchantment enchant, GuiItem guiItem) { return event -> { event.setCancelled(true); @@ -152,9 +181,37 @@ public class EnchantSelectSettingGui extends AbstractSettingGui { @Override public boolean hadChange() { - Set baseGroup = this.enchantContainer.getSelectedEnchantments(); + Set baseGroup = this.enchantContainer.getSelectedEnchantments(); return baseGroup.size() != this.selectedEnchant.size() || !baseGroup.containsAll(this.selectedEnchant); } + + // Unused methods and class + public static class DummyFactory extends AbstractSettingGui.SettingGuiFactory{ + protected DummyFactory(@NotNull String configPath, @NotNull ConfigHolder config) { + super(configPath, config); + } + @Override + public Gui create() { + return null; + } + } + @Override + protected List getCreateItemLore() { + return Collections.emptyList(); + } + @Override + protected Consumer getCreateClickConsumer() { + return null; + } + @Override + protected String createItemName() { + return null; + } + @Override + protected DummyFactory createFactory(CAEnchantment generic) { + return null; + } + } diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/EnumSettingGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/EnumSettingGui.java new file mode 100644 index 0000000..5bf4c24 --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/EnumSettingGui.java @@ -0,0 +1,242 @@ +package xyz.alexcrea.cuanvil.gui.config.settings; + +import com.github.stefvanschie.inventoryframework.gui.GuiItem; +import com.github.stefvanschie.inventoryframework.gui.type.util.Gui; +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.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.NotNull; +import xyz.alexcrea.cuanvil.config.ConfigHolder; +import xyz.alexcrea.cuanvil.gui.ValueUpdatableGui; +import xyz.alexcrea.cuanvil.gui.util.GuiGlobalItems; +import xyz.alexcrea.cuanvil.gui.util.GuiSharedConstant; + +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; + +public class EnumSettingGui & EnumSettingGui.ConfigurableEnum> extends AbstractSettingGui { + + private final EnumSettingFactory holder; + private final T before; + private T now; + + /** + * Create an item setting config gui. + * + * @param holder Configuration factory of this setting. + * @param now The defined value of this setting. + */ + protected EnumSettingGui(EnumSettingFactory holder, T now) { + super(3, holder.getTitle(), holder.parent); + this.holder = holder; + this.before = now; + this.now = now; + + prepareStaticItems(); + updateValueDisplay(); + } + + @Override + public Pattern getGuiPattern() { + return new Pattern( + GuiSharedConstant.EMPTY_GUI_FULL_LINE, + "D000v0000", + "B0000000S" + ); + } + + + public void prepareStaticItems(){ + prepareReturnToDefault(); + } + + + protected GuiItem returnToDefault; + + /** + * Prepare "return to default value" gui item. + */ + protected void prepareReturnToDefault() { + ItemStack item = new ItemStack(Material.COMMAND_BLOCK); + ItemMeta meta = item.getItemMeta(); + assert meta != null; + + meta.setDisplayName("§eReset to default value"); + meta.setLore(Collections.singletonList("§7Default value is §e" + holder.getDefault().configurationGuiName())); + item.setItemMeta(meta); + returnToDefault = new GuiItem(item, event -> { + event.setCancelled(true); + now = holder.getDefault(); + updateValueDisplay(); + update(); + }, CustomAnvil.instance); + } + + /** + * Update item using the setting value to match the new value + */ + protected void updateValueDisplay() { + PatternPane pane = getPane(); + + // Get displayed value for this config. + ItemStack displayedItem = now.configurationGuiItem(); + + GuiItem resultItem = new GuiItem(displayedItem, selectNext(), CustomAnvil.instance); + pane.bindItem('v', resultItem); + + // reset to default + GuiItem returnToDefault; + if (now != holder.getDefault()) { + returnToDefault = this.returnToDefault; + } else { + returnToDefault = GuiGlobalItems.backgroundItem(); + } + pane.bindItem('D', returnToDefault); + + } + + /** + * @return A consumer to update the current setting's value. + */ + protected Consumer selectNext() { + return event -> { + event.setCancelled(true); + + this.now = this.holder.next(this.now); + + updateValueDisplay(); + update(); + }; + + } + + @Override + public boolean onSave() { + holder.config.getConfig().set(holder.configPath, this.now.configName()); + + if (GuiSharedConstant.TEMPORARY_DO_SAVE_TO_DISK_EVERY_CHANGE) { + return holder.config.saveToDisk(GuiSharedConstant.TEMPORARY_DO_BACKUP_EVERY_SAVE); + } + return true; + } + + @Override + public boolean hadChange() { + return !now.equals(before); + } + + + /** + * A factory for an enum setting gui that hold setting's information. + */ + public abstract static class EnumSettingFactory & ConfigurableEnum> extends SettingGuiFactory { + @NotNull + String title; + @NotNull + ValueUpdatableGui parent; + + /** + * Constructor for an enum settings gui factory + * + * @param title The title of the gui. + * @param parent Parent gui to go back when completed. + * @param configPath Configuration path of this setting. + * @param config Configuration holder of this setting. + */ + protected EnumSettingFactory( + @NotNull String title, @NotNull ValueUpdatableGui parent, + @NotNull String configPath, @NotNull ConfigHolder config) { + super(configPath, config); + this.title = title; + this.parent = parent; + + } + /** + * @return Get setting's gui title. + */ + @NotNull + public String getTitle() { + return title; + } + + /** + * @return The configured value for the associated setting. + */ + @NotNull + public abstract T getConfiguredValue(); + + @NotNull + public abstract List getDisplayLore(T value); + + /** + * @return Next value for a given enum + */ + @NotNull + public T next(@NotNull T now){ + Class clazz = now.getDeclaringClass(); + T[] values = clazz.getEnumConstants(); + + int index = now.ordinal(); + if(index == values.length - 1) + return values[0]; + + return values[index + 1]; + } + + /** + * Get default value value + * @return default value + */ + @NotNull + public abstract T getDefault(); + + @Override + public Gui create() { + // Get value or default + T now = getConfiguredValue(); + + // create new gui + return new EnumSettingGui<>(this, now); + } + + /** + * Create a new enum setting GuiItem. + * This item will create and open an enum setting GUI from the factory. + * + * @param name Name of the display. + * @return A formatted GuiItem that will create and open a GUI for the enum setting. + */ + public GuiItem getItem(@NotNull Material material, @NotNull String name) { + T value = getConfiguredValue(); + + ItemStack item = new ItemStack(material); + ItemMeta meta = item.getItemMeta(); + assert meta != null; + + meta.setDisplayName(name); + meta.setLore(getDisplayLore(value)); + meta.addItemFlags(ItemFlag.values()); + + item.setItemMeta(meta); + + return GuiGlobalItems.openSettingGuiItem(item, this); + } + } + + public interface ConfigurableEnum { + + String configName(); + + ItemStack configurationGuiItem(); + String configurationGuiName(); + + + } + +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/GroupSelectSettingGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/GroupSelectSettingGui.java index 9808a8b..69986a0 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/GroupSelectSettingGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/GroupSelectSettingGui.java @@ -17,6 +17,7 @@ import xyz.alexcrea.cuanvil.config.ConfigHolder; import xyz.alexcrea.cuanvil.group.AbstractMaterialGroup; import xyz.alexcrea.cuanvil.gui.ValueUpdatableGui; import xyz.alexcrea.cuanvil.gui.config.SelectGroupContainer; +import xyz.alexcrea.cuanvil.gui.config.list.ElementListConfigGui; import xyz.alexcrea.cuanvil.gui.util.GuiSharedConstant; import xyz.alexcrea.cuanvil.util.CasedStringUtil; @@ -50,18 +51,22 @@ public class GroupSelectSettingGui extends AbstractSettingGui { @Override protected Pattern getGuiPattern() { return new Pattern( - GuiSharedConstant.EMPTY_GUI_FULL_LINE, - GuiSharedConstant.EMPTY_GUI_FULL_LINE, - GuiSharedConstant.EMPTY_GUI_FULL_LINE, - GuiSharedConstant.EMPTY_GUI_FULL_LINE, - GuiSharedConstant.EMPTY_GUI_FULL_LINE, + GuiSharedConstant.UPPER_FILLER_FULL_PLANE, + GuiSharedConstant.EMPTY_FILLER_FULL_LINE, + GuiSharedConstant.EMPTY_FILLER_FULL_LINE, + GuiSharedConstant.EMPTY_FILLER_FULL_LINE, + GuiSharedConstant.EMPTY_FILLER_FULL_LINE, "B1111111S" ); } protected void initGroups() { // Add enchantment gui item - OutlinePane filledEnchant = new OutlinePane(0, 0, 9, 5); + OutlinePane filledEnchant = new OutlinePane( + ElementListConfigGui.LIST_FILLER_START_X, + ElementListConfigGui.LIST_FILLER_START_Y, + ElementListConfigGui.LIST_FILLER_LENGTH, + ElementListConfigGui.LIST_FILLER_HEIGHT); filledEnchant.setPriority(Pane.Priority.HIGH); filledEnchant.align(OutlinePane.Alignment.BEGIN); filledEnchant.setOrientation(Orientable.Orientation.HORIZONTAL); @@ -91,8 +96,8 @@ public class GroupSelectSettingGui extends AbstractSettingGui { return guiItem; } - private static final List TRUE_LORE = Collections.singletonList("\u00A77Value: \u00A7aSelected"); - private static final List FALSE_LORE = Collections.singletonList("\u00A77Value: \u00A7cNot Selected"); + private static final List TRUE_LORE = Collections.singletonList("§7Value: §aSelected"); + private static final List FALSE_LORE = Collections.singletonList("§7Value: §cNot Selected"); public void setGroupItemMeta(ItemStack item, String name, boolean isIn) { ItemMeta meta = item.getItemMeta(); @@ -105,7 +110,7 @@ public class GroupSelectSettingGui extends AbstractSettingGui { assert meta != null; } - meta.setDisplayName("\u00A7" + (isIn ? 'a' : 'c') + CasedStringUtil.snakeToUpperSpacedCase(name)); + meta.setDisplayName("§" + (isIn ? 'a' : 'c') + CasedStringUtil.snakeToUpperSpacedCase(name)); if (isIn) { meta.addEnchant(Enchantment.DAMAGE_UNDEAD, 1, true); meta.setLore(TRUE_LORE); 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 9fabf04..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 @@ -1,6 +1,7 @@ package xyz.alexcrea.cuanvil.gui.config.settings; import com.github.stefvanschie.inventoryframework.gui.GuiItem; +import com.github.stefvanschie.inventoryframework.gui.type.util.Gui; import com.github.stefvanschie.inventoryframework.pane.PatternPane; import com.github.stefvanschie.inventoryframework.pane.util.Pattern; import io.delilaheve.CustomAnvil; @@ -70,8 +71,9 @@ public class IntSettingsGui extends AbstractSettingGui { ItemMeta meta = item.getItemMeta(); assert meta != null; - meta.setDisplayName("\u00A7eReset to default value"); - meta.setLore(Collections.singletonList("\u00A77Default value is \u00A7e" + holder.defaultVal)); + meta.setDisplayName("§eReset to default value"); + 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); @@ -85,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("\u00A7e" + now + " \u00A7f-> \u00A7e" + planned + " \u00A7r(\u00A7c-" + (now - planned) + "\u00A7r)"); - 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("\u00A7e" + now + " \u00A7f-> \u00A7e" + planned + " \u00A7r(\u00A7a+" + (planned - now) + "\u00A7r)"); - 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); } @@ -130,7 +114,7 @@ public class IntSettingsGui extends AbstractSettingGui { ItemMeta resultMeta = resultPaper.getItemMeta(); assert resultMeta != null; - resultMeta.setDisplayName("\u00A7fValue: \u00A7e" + now); + resultMeta.setDisplayName("§fValue: §e" + holder.valueDisplayName(ValueDisplayType.CURRENT, now)); resultMeta.setLore(holder.displayLore); resultPaper.setItemMeta(resultMeta); @@ -148,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); } /** @@ -217,21 +215,21 @@ public class IntSettingsGui extends AbstractSettingGui { // Get material properties Material stepMat; - StringBuilder stepName = new StringBuilder("\u00A7"); + StringBuilder stepName = new StringBuilder("§"); List stepLore; Consumer clickEvent; if (stepValue == step) { stepMat = Material.GREEN_STAINED_GLASS_PANE; stepName.append('a'); - stepLore = Collections.singletonList("\u00A77Value is changing by " + stepValue); + stepLore = Collections.singletonList("§7Value is changing by " + stepValue); clickEvent = GuiGlobalActions.stayInPlace; } else { stepMat = Material.RED_STAINED_GLASS_PANE; stepName.append('c'); - stepLore = Collections.singletonList("\u00A77Click here to change the value by " + stepValue); + stepLore = Collections.singletonList("§7Click here to change the value by " + stepValue); clickEvent = updateStepValue(stepValue); } - stepName.append("Step of: \u00A7e").append(stepValue); + stepName.append("Step of: §e").append(stepValue); // Create item stack then gui item ItemStack item = new ItemStack(stepMat); @@ -274,38 +272,11 @@ public class IntSettingsGui extends AbstractSettingGui { return now != before; } - /** - * Create an int setting factory from setting's parameters. - * - * @param title The title of the gui. - * @param parent Parent gui to go back when completed. - * @param configPath Configuration path of this setting. - * @param config Configuration holder of this setting. - * @param displayLore Gui display item lore. - * @param min Minimum value of this setting. - * @param max Maximum value of this setting. - * @param defaultVal Default value if not found on the config. - * @param steps List of step the value can increment/decrement. - * List's size should be between 1 (included) and 5 (included). - * it is visually preferable to have an odd number of step. - * If step only contain 1 value, no step item should be displayed. - * @return A factory for an int setting gui. - */ - public static IntSettingFactory intFactory(@NotNull String title, ValueUpdatableGui parent, - String configPath, ConfigHolder config, - @Nullable List displayLore, - int min, int max, int defaultVal, int... steps) { - return new IntSettingFactory( - title, parent, - configPath, config, - displayLore, - min, max, defaultVal, steps); - } - /** * A factory for an int setting gui that hold setting's information. */ public static class IntSettingFactory extends SettingGuiFactory { + @NotNull String title; @NotNull @@ -334,7 +305,7 @@ public class IntSettingsGui extends AbstractSettingGui { * it is visually preferable to have an odd number of step. * If step only contain 1 value, no step item should be displayed. */ - protected IntSettingFactory( + public IntSettingFactory( @NotNull String title, @NotNull ValueUpdatableGui parent, @NotNull String configPath, @NotNull ConfigHolder config, @Nullable List displayLore, @@ -370,7 +341,7 @@ public class IntSettingsGui extends AbstractSettingGui { } @Override - public AbstractSettingGui create() { + public Gui create() { // Get value or default int now = getConfiguredValue(); // create new gui @@ -392,10 +363,10 @@ public class IntSettingsGui extends AbstractSettingGui { ) { // Get item properties int value = getConfiguredValue(); - StringBuilder itemName = new StringBuilder("\u00A7a").append(name); + StringBuilder itemName = new StringBuilder("§a").append(name); return GuiGlobalItems.createGuiItemFromProperties(this, itemMat, itemName, - "\u00A7e" + value, + "§e" + value, this.displayLore, true); } @@ -415,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/ItemSettingGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/ItemSettingGui.java index 9dccde2..3df2af8 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/ItemSettingGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/ItemSettingGui.java @@ -1,6 +1,7 @@ package xyz.alexcrea.cuanvil.gui.config.settings; import com.github.stefvanschie.inventoryframework.gui.GuiItem; +import com.github.stefvanschie.inventoryframework.gui.type.util.Gui; import com.github.stefvanschie.inventoryframework.pane.PatternPane; import com.github.stefvanschie.inventoryframework.pane.util.Pattern; import io.delilaheve.CustomAnvil; @@ -61,7 +62,7 @@ public class ItemSettingGui extends AbstractSettingGui { public void prepareStaticItems(){ prepareReturnToDefault(); - GuiItem temporaryLeave = GuiGlobalItems.temporaryCloseGuiToSelectItem(Material.YELLOW_TERRACOTTA, this); + GuiItem temporaryLeave = GuiGlobalItems.temporaryCloseGuiToSelectItem(Material.YELLOW_STAINED_GLASS_PANE, this); getPane().bindItem('s', temporaryLeave); } @@ -76,8 +77,8 @@ public class ItemSettingGui extends AbstractSettingGui { ItemMeta meta = item.getItemMeta(); assert meta != null; - meta.setDisplayName("\u00A7eReset to default value"); - meta.setLore(Collections.singletonList("\u00A77Default value is \u00A7e" + holder.defaultVal)); + meta.setDisplayName("§eReset to default value"); + meta.setLore(Collections.singletonList("§7Default value is §e" + holder.defaultVal)); item.setItemMeta(meta); returnToDefault = new GuiItem(item, event -> { event.setCancelled(true); @@ -87,7 +88,7 @@ public class ItemSettingGui extends AbstractSettingGui { }, CustomAnvil.instance); } - protected final static List CLICK_LORE = Collections.singletonList("\u00A77Click Here with an item to change the value"); + protected final static List CLICK_LORE = Collections.singletonList("§7Click Here with an item to change the value"); /** * Update item using the setting value to match the new value @@ -104,7 +105,7 @@ public class ItemSettingGui extends AbstractSettingGui { ItemMeta valueMeta = displayedItem.getItemMeta(); assert valueMeta != null; - valueMeta.setDisplayName("\u00A74NO ITEM SET"); + valueMeta.setDisplayName("§4NO ITEM SET"); valueMeta.setLore(CLICK_LORE); displayedItem.setItemMeta(valueMeta); @@ -162,27 +163,6 @@ public class ItemSettingGui extends AbstractSettingGui { return !now.equals(before); } - /** - * Create aa item setting factory from setting's parameters. - * - * @param title The title of the gui. - * @param parent Parent gui to go back when completed. - * @param configPath Configuration path of this setting. - * @param config Configuration holder of this setting. - * @param defaultVal Default value if not found on the config. - * @param displayLore Gui display item lore. - * @return A factory for an item setting gui. - */ - public static ItemSettingGui.ItemSettingFactory itemFactory(@NotNull String title, @NotNull ValueUpdatableGui parent, - @NotNull String configPath, @NotNull ConfigHolder config, - @Nullable ItemStack defaultVal, - String... displayLore) { - return new ItemSettingGui.ItemSettingFactory( - title, parent, - configPath, config, - defaultVal, displayLore); - } - /** * A factory for an item setting gui that hold setting's information. */ @@ -206,7 +186,7 @@ public class ItemSettingGui extends AbstractSettingGui { * @param defaultVal Default value if not found on the config. * @param displayLore Gui display item lore. */ - protected ItemSettingFactory( + public ItemSettingFactory( @NotNull String title, @NotNull ValueUpdatableGui parent, @NotNull String configPath, @NotNull ConfigHolder config, @Nullable ItemStack defaultVal, @@ -240,7 +220,7 @@ public class ItemSettingGui extends AbstractSettingGui { } @Override - public AbstractSettingGui create() { + public Gui create() { // Get current value or default ItemStack now = getConfiguredValue(); // create new gui @@ -265,7 +245,7 @@ public class ItemSettingGui extends AbstractSettingGui { ItemMeta meta = item.getItemMeta(); assert meta != null; - meta.setDisplayName("\u00A7a" + name); + meta.setDisplayName("§a" + name); meta.setLore(getDisplayLore()); meta.addItemFlags(ItemFlag.values()); 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 9309bda..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; @@ -57,11 +59,11 @@ 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("\u00A7cSomething went wrong while saving the change of value."); + player.sendMessage("§cSomething went wrong while saving the change of value."); } // Return to parent @@ -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( "Remove " + materialName, - "\u00A77Confirm Remove " + materialName.toLowerCase() + " from this list.", + "§7Confirm Remove " + materialName.toLowerCase() + " from this list.", this, this, () -> { removeMaterial(material); @@ -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/SettingGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/SettingGui.java new file mode 100644 index 0000000..a6d2784 --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/SettingGui.java @@ -0,0 +1,33 @@ +package xyz.alexcrea.cuanvil.gui.config.settings; + +import com.github.stefvanschie.inventoryframework.gui.type.util.Gui; + +public interface SettingGui { + + /** + * Called when the associated setting need to be saved. + * + * @return true if the save was successful. false otherwise + */ + boolean onSave(); + + /** + * If this function return true + * the gui assume the associated setting can be saved. + * + * @return true if there is a change to the setting. false otherwise + */ + boolean hadChange(); + + interface SettingGuiFactory { + + /** + * Create a gui using setting parameters and current setting value. + * + * @return A new instance of the implemented setting gui. + */ + Gui create(); + + } + +} 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 new file mode 100644 index 0000000..4345aa1 --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/WorkPenaltyTypeSettingGui.java @@ -0,0 +1,252 @@ +package xyz.alexcrea.cuanvil.gui.config.settings; + +import com.github.stefvanschie.inventoryframework.gui.GuiItem; +import com.github.stefvanschie.inventoryframework.pane.PatternPane; +import com.github.stefvanschie.inventoryframework.pane.util.Pattern; +import io.delilaheve.CustomAnvil; +import io.delilaheve.util.ConfigOptions; +import org.bukkit.Material; +import org.bukkit.configuration.file.FileConfiguration; +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 java.util.ArrayList; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; + +public class WorkPenaltyTypeSettingGui extends AbstractSettingGui { + + private static final String INCREASING_EXPLANATION = "§eIncreasing§7: will penalty be increased (in item)"; + private static final String ADDING_EXPLANATION = "§eAdditive§7: will penalty be added to the cost"; + + private static final String SHARED_EXPLANATION = "§eShared§7: Vanilla, shared penalty. it will be kept from before the plugin installation."; + private static final String EXCLUSIVE_EXPLANATION = "§eExclusive§7: Custom, per anvil use type penalty. it will be lost after plugin uninstallation"; + + private final @NotNull WorkPenaltyType currentType; + private final @NotNull Map items; + + public WorkPenaltyTypeSettingGui(@NotNull BasicConfigGui parent) { + super(4, "§8Work Penalty Type", parent); + + this.currentType = ConfigOptions.INSTANCE.getWorkPenaltyType(); + this.items = new EnumMap<>(this.currentType.getPartMap()); + + for (AnvilUseType type : useTypes.keySet()) { + updateGuiForType(type); + } + } + + public static GuiItem getDisplayItem(@NotNull BasicConfigGui parent, + @NotNull Material itemMat, + @NotNull String name) { + List displayLore = new ArrayList<>(); + + displayLore.add("§7Work penalty increase the price for every anvil use."); + displayLore.add("§7This config allow you to choose the comportment of work penalty."); + displayLore.add(INCREASING_EXPLANATION); + displayLore.add(ADDING_EXPLANATION); + displayLore.add(""); + displayLore.add("§7About shared/exclusive penalty:"); + displayLore.add(SHARED_EXPLANATION); + displayLore.add(EXCLUSIVE_EXPLANATION); + + ItemStack item = new ItemStack(itemMat); + + ItemMeta meta = item.getItemMeta(); + meta.setDisplayName(name); + meta.setLore(displayLore); + + item.setItemMeta(meta); + + return new GuiItem(item, (event) -> { + event.setCancelled(true); + HumanEntity player = event.getWhoClicked(); + + // Do not allow to open inventory if player do not have edit configuration permission + if (!player.hasPermission(CustomAnvil.editConfigPermission)) { + player.closeInventory(); + player.sendMessage(GuiGlobalActions.NO_EDIT_PERM); + return; + } + new WorkPenaltyTypeSettingGui(parent).show(player); + }, CustomAnvil.instance); + } + + private static final Map useTypes = + Map.of( + AnvilUseType.RENAME_ONLY, "a1z9Z", + AnvilUseType.MERGE, "b2y8Y", + AnvilUseType.UNIT_REPAIR, "c3x7X", + AnvilUseType.CUSTOM_CRAFT, "d4w6W" + ); + + @Override + protected Pattern getGuiPattern() { + return new Pattern( // Yeah that a mess + "00a1z9Z00", + "00b2y8Y00", + "00c3x7X00", + "B004w600S" + ); + } + + public void updateGuiForType(AnvilUseType type) { + PatternPane pane = getPane(); + + String typeVals = useTypes.get(type); + + char increment = typeVals.charAt(0); + char additive = typeVals.charAt(1); + char display = typeVals.charAt(2); + char exclusiveIncrement = typeVals.charAt(3); + char exclusiveAdditive = typeVals.charAt(4); + + WorkPenaltyType.WorkPenaltyPart part = items.get(type); + String increasingStr = (part.penaltyIncrease() ? "§a" : "§c") + "Increasing"; + String additiveStr = (part.penaltyAdditive() ? "§a" : "§c") + "Additive"; + String exclusiveIncreasingStr = (part.exclusivePenaltyIncrease() ? "§a" : "§c") + "Increasing"; + String exclusiveAdditiveStr = (part.exclusivePenaltyAdditive() ? "§a" : "§c") + "Additive"; + + // Display item + ItemStack displayItem = new ItemStack(type.getDisplayMat()); + + ArrayList displayLore = new ArrayList<>(); + displayLore.add("§eShared§7: " + additiveStr + " §7| " + increasingStr); + displayLore.add("§eExclusive§7: " + exclusiveAdditiveStr + " §7| " + exclusiveIncreasingStr); + + ItemMeta meta = displayItem.getItemMeta(); + meta.setDisplayName("§e" + type.getDisplayName()); + meta.setLore(displayLore); + displayItem.setItemMeta(meta); + + pane.bindItem(display, new GuiItem(displayItem, (event) -> { + event.setCancelled(true); + })); + + // Can probably put this in a function but this works so + // "Increment" item + ItemStack incrementItem = new ItemStack(part.penaltyIncrease() ? Material.GREEN_TERRACOTTA : Material.RED_TERRACOTTA); + + meta = incrementItem.getItemMeta(); + meta.setDisplayName(increasingStr); + meta.setLore(List.of(INCREASING_EXPLANATION)); + meta.setLore(List.of(SHARED_EXPLANATION)); + incrementItem.setItemMeta(meta); + + pane.bindItem(increment, new GuiItem(incrementItem, (event) -> { + event.setCancelled(true); + + WorkPenaltyType.WorkPenaltyPart newPart = new WorkPenaltyType.WorkPenaltyPart( + !part.penaltyIncrease(), part.penaltyAdditive(), + part.exclusivePenaltyIncrease(), part.exclusivePenaltyAdditive()); + items.replace(type, newPart); + updateGuiForType(type); + update(); + })); + + // "Additive" item + ItemStack additiveItem = new ItemStack(part.penaltyAdditive() ? Material.GREEN_TERRACOTTA : Material.RED_TERRACOTTA); + + meta = additiveItem.getItemMeta(); + meta.setDisplayName(additiveStr); + meta.setLore(List.of(ADDING_EXPLANATION)); + meta.setLore(List.of(SHARED_EXPLANATION)); + additiveItem.setItemMeta(meta); + + pane.bindItem(additive, new GuiItem(additiveItem, (event) -> { + event.setCancelled(true); + + WorkPenaltyType.WorkPenaltyPart newPart = new WorkPenaltyType.WorkPenaltyPart( + part.penaltyIncrease(), !part.penaltyAdditive(), + part.exclusivePenaltyIncrease(), part.exclusivePenaltyAdditive()); + items.replace(type, newPart); + updateGuiForType(type); + update(); + })); + + // exclusive "Increment" item + ItemStack exclusiveIncrementItem = new ItemStack(part.exclusivePenaltyIncrease() ? Material.GREEN_TERRACOTTA : Material.RED_TERRACOTTA); + + meta = exclusiveIncrementItem.getItemMeta(); + meta.setDisplayName(exclusiveIncreasingStr); + meta.setLore(List.of(INCREASING_EXPLANATION)); + meta.setLore(List.of(EXCLUSIVE_EXPLANATION)); + exclusiveIncrementItem.setItemMeta(meta); + + pane.bindItem(exclusiveIncrement, new GuiItem(exclusiveIncrementItem, (event) -> { + event.setCancelled(true); + + WorkPenaltyType.WorkPenaltyPart newPart = new WorkPenaltyType.WorkPenaltyPart( + part.penaltyIncrease(), part.penaltyAdditive(), + !part.exclusivePenaltyIncrease(), part.exclusivePenaltyAdditive()); + items.replace(type, newPart); + updateGuiForType(type); + update(); + })); + + // exclusive "Additive" item + ItemStack exclusiveAdditiveItem = new ItemStack(part.exclusivePenaltyAdditive() ? Material.GREEN_TERRACOTTA : Material.RED_TERRACOTTA); + + meta = exclusiveAdditiveItem.getItemMeta(); + meta.setDisplayName(exclusiveAdditiveStr); + meta.setLore(List.of(ADDING_EXPLANATION)); + meta.setLore(List.of(EXCLUSIVE_EXPLANATION)); + exclusiveAdditiveItem.setItemMeta(meta); + + pane.bindItem(exclusiveAdditive, new GuiItem(exclusiveAdditiveItem, (event) -> { + event.setCancelled(true); + + WorkPenaltyType.WorkPenaltyPart newPart = new WorkPenaltyType.WorkPenaltyPart( + part.penaltyIncrease(), part.penaltyAdditive(), + part.exclusivePenaltyIncrease(), !part.exclusivePenaltyAdditive()); + items.replace(type, newPart); + updateGuiForType(type); + update(); + })); + } + + @Override + public boolean onSave() { + return saveWorkPenalty(items); + } + + public static boolean saveWorkPenalty(Map partEnum) { + ConfigHolder configHolder = ConfigHolder.DEFAULT_CONFIG; + FileConfiguration config = configHolder.getConfig(); + + partEnum.forEach((key, value) -> { + String partPath = key.getPath(); + + if (key.getDefaultPenalty().equals(value)) { + config.set(partPath, null); + return; + } + + config.set(partPath + '.' + ConfigOptions.WORK_PENALTY_INCREASE, value.penaltyIncrease()); + config.set(partPath + '.' + ConfigOptions.WORK_PENALTY_ADDITIVE, value.penaltyAdditive()); + config.set(partPath + '.' + ConfigOptions.EXCLUSIVE_WORK_PENALTY_INCREASE, value.exclusivePenaltyIncrease()); + config.set(partPath + '.' + ConfigOptions.EXCLUSIVE_WORK_PENALTY_ADDITIVE, value.exclusivePenaltyAdditive()); + }); + + return configHolder.saveToDisk(true); + } + + @Override + public boolean hadChange() { + for (AnvilUseType type : items.keySet()) { + if (!currentType.getPenaltyInfo(type).equals(items.get(type))) { + return true; + } + } + + return false; + } +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/util/GuiGlobalActions.java b/src/main/java/xyz/alexcrea/cuanvil/gui/util/GuiGlobalActions.java index 6be8d83..694fa14 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/util/GuiGlobalActions.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/util/GuiGlobalActions.java @@ -6,7 +6,7 @@ import org.bukkit.entity.HumanEntity; import org.bukkit.event.inventory.InventoryClickEvent; import org.jetbrains.annotations.NotNull; import xyz.alexcrea.cuanvil.gui.ValueUpdatableGui; -import xyz.alexcrea.cuanvil.gui.config.settings.AbstractSettingGui; +import xyz.alexcrea.cuanvil.gui.config.settings.SettingGui; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; @@ -17,12 +17,12 @@ import java.util.function.Consumer; */ public class GuiGlobalActions { - public final static String NO_EDIT_PERM = "§cYou do not have permission to edit the config"; + public static final String NO_EDIT_PERM = "§cYou do not have permission to edit the config"; /** * A Consumer that should be used if the item goal is to do nothing on click. */ - public final static Consumer stayInPlace = (event) -> event.setCancelled(true); + public static final Consumer stayInPlace = event -> event.setCancelled(true); /** * Create a consumer to create and open a new GUI. @@ -80,7 +80,7 @@ public class GuiGlobalActions { * @param factory The setting gui factory. * @return A consumer to create and open a new setting GUI. */ - public static @NotNull Consumer openSettingGuiAction(AbstractSettingGui.SettingGuiFactory factory) { + public static @NotNull Consumer openSettingGuiAction(SettingGui.SettingGuiFactory factory) { return event -> { event.setCancelled(true); Gui gui = factory.create(); @@ -119,7 +119,7 @@ public class GuiGlobalActions { * @return A consumer to open a global GUI. */ public static @NotNull Consumer saveSettingAction( - @NotNull AbstractSettingGui setting, + @NotNull SettingGui setting, @NotNull ValueUpdatableGui goal) { return event -> { event.setCancelled(true); @@ -133,12 +133,12 @@ public class GuiGlobalActions { // Save setting if (!setting.onSave()) { - player.sendMessage("\u00A7cSomething went wrong while saving the change of value."); + player.sendMessage("§cSomething went wrong while saving the change of value."); } // Update gui for those who have it open. goal.updateGuiValues(); // Then show - goal.show(player); + goal.getConnectedGui().show(player); }; } diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/util/GuiGlobalItems.java b/src/main/java/xyz/alexcrea/cuanvil/gui/util/GuiGlobalItems.java index 828eb90..79f1462 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/util/GuiGlobalItems.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/util/GuiGlobalItems.java @@ -11,7 +11,7 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.jetbrains.annotations.NotNull; import xyz.alexcrea.cuanvil.gui.ValueUpdatableGui; -import xyz.alexcrea.cuanvil.gui.config.settings.AbstractSettingGui; +import xyz.alexcrea.cuanvil.gui.config.settings.SettingGui; import java.util.ArrayList; import java.util.Collections; @@ -30,7 +30,7 @@ public class GuiGlobalItems { ItemMeta meta = BACK_ITEM.getItemMeta(); assert meta != null; - meta.setDisplayName("\u00A7cBack"); + meta.setDisplayName("§cBack"); BACK_ITEM.setItemMeta(meta); } @@ -82,7 +82,7 @@ public class GuiGlobalItems { ItemMeta meta = item.getItemMeta(); assert meta != null; - meta.setDisplayName("\u00A7c"); + meta.setDisplayName("§c"); item.setItemMeta(meta); return new GuiItem(item, GuiGlobalActions.stayInPlace, CustomAnvil.instance); } @@ -132,14 +132,14 @@ public class GuiGlobalItems { * @return A save setting item. */ public static GuiItem saveItem( - @NotNull AbstractSettingGui setting, + @NotNull SettingGui setting, @NotNull ValueUpdatableGui goal) { ItemStack item = new ItemStack(DEFAULT_SAVE_ITEM); ItemMeta meta = item.getItemMeta(); assert meta != null; - meta.setDisplayName("\u00A7aSave"); + meta.setDisplayName("§aSave"); item.setItemMeta(meta); return new GuiItem(item, GuiGlobalActions.saveSettingAction(setting, goal), @@ -154,7 +154,7 @@ public class GuiGlobalItems { ItemMeta meta = item.getItemMeta(); assert meta != null; - meta.setDisplayName("\u00A77No change. can't save."); + meta.setDisplayName("§7No change. can't save."); item.setItemMeta(meta); NO_CHANGE_ITEM = new GuiItem(item, GuiGlobalActions.stayInPlace, CustomAnvil.instance); } @@ -179,13 +179,13 @@ public class GuiGlobalItems { */ public static GuiItem openSettingGuiItem( @NotNull ItemStack item, - @NotNull AbstractSettingGui.SettingGuiFactory factory + @NotNull SettingGui.SettingGuiFactory factory ) { return new GuiItem(item, GuiGlobalActions.openSettingGuiAction(factory), CustomAnvil.instance); } // Prefix of the one line lore that will be added to setting's item. - public static final String SETTING_ITEM_LORE_PREFIX = "\u00A77value: "; + public static final String SETTING_ITEM_LORE_PREFIX = "§7value: "; /** * Create an arbitrary GuiItem from a unique setting and item's property. @@ -199,7 +199,7 @@ public class GuiGlobalItems { * @return A formatted GuiItem that will create and open a GUI for the setting. */ public static GuiItem createGuiItemFromProperties( - @NotNull AbstractSettingGui.SettingGuiFactory factory, + @NotNull SettingGui.SettingGuiFactory factory, @NotNull Material itemMat, @NotNull StringBuilder itemName, @NotNull Object value, @@ -247,8 +247,8 @@ public class GuiGlobalItems { ItemMeta meta = item.getItemMeta(); assert meta != null; - meta.setDisplayName("\u00A7eTemporary close this menu"); - meta.setLore(Collections.singletonList("\u00A77Allow you to chose other item then return here.")); + meta.setDisplayName("§eTemporary close this menu"); + meta.setLore(Collections.singletonList("§7Allow you to chose other item then return here.")); item.setItemMeta(meta); return new GuiItem(item, event -> { @@ -263,7 +263,7 @@ public class GuiGlobalItems { }); - player.sendMessage("\u00A7eWrite something in chat to return to the item config menu."); + player.sendMessage("§eWrite something in chat to return to the item config menu."); player.closeInventory(); }, CustomAnvil.instance); } diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/util/GuiSharedConstant.java b/src/main/java/xyz/alexcrea/cuanvil/gui/util/GuiSharedConstant.java index 1c33af2..11f3657 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/util/GuiSharedConstant.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/util/GuiSharedConstant.java @@ -7,27 +7,21 @@ import com.github.stefvanschie.inventoryframework.pane.util.Pattern; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; -import xyz.alexcrea.cuanvil.enchant.WrappedEnchantment; import xyz.alexcrea.cuanvil.gui.config.MainConfigGui; import java.util.Arrays; import java.util.Collections; -import java.util.Comparator; -import java.util.List; public class GuiSharedConstant { - public static final List SORTED_ENCHANTMENT_LIST; - - static { - SORTED_ENCHANTMENT_LIST = Arrays.asList(WrappedEnchantment.values()); - SORTED_ENCHANTMENT_LIST.sort(Comparator.comparing(ench -> ench.getKey().getKey())); - } + private GuiSharedConstant(){} public static final Material SECONDARY_BACKGROUND_MATERIAL = Material.BLACK_STAINED_GLASS_PANE; public static final GuiItem SECONDARY_BACKGROUND_ITEM = GuiGlobalItems.backgroundItem(GuiSharedConstant.SECONDARY_BACKGROUND_MATERIAL); + public static final String UPPER_FILLER_FULL_PLANE = "111111111"; public static final String EMPTY_GUI_FULL_LINE = "000000000"; + public static final String EMPTY_FILLER_FULL_LINE = "100000001"; // Temporary values, until I get something better. public static final boolean TEMPORARY_DO_SAVE_TO_DISK_EVERY_CHANGE = true; @@ -37,11 +31,11 @@ public class GuiSharedConstant { static { Pattern pattern = new Pattern( - GuiSharedConstant.EMPTY_GUI_FULL_LINE, - GuiSharedConstant.EMPTY_GUI_FULL_LINE, - GuiSharedConstant.EMPTY_GUI_FULL_LINE, - GuiSharedConstant.EMPTY_GUI_FULL_LINE, - GuiSharedConstant.EMPTY_GUI_FULL_LINE, + GuiSharedConstant.UPPER_FILLER_FULL_PLANE, + GuiSharedConstant.EMPTY_FILLER_FULL_LINE, + GuiSharedConstant.EMPTY_FILLER_FULL_LINE, + GuiSharedConstant.EMPTY_FILLER_FULL_LINE, + GuiSharedConstant.EMPTY_FILLER_FULL_LINE, "B11111111" ); BACK_TO_MAIN_MENU_BIG_LIST_DISPLAY_BACKGROUND_PANE = new PatternPane(0, 0, 9, 6, Pane.Priority.LOW, pattern); @@ -62,25 +56,25 @@ public class GuiSharedConstant { ItemMeta meta = CANCEL_ITEM.getItemMeta(); assert meta != null; - meta.setDisplayName("\u00A7cCancel"); - meta.setLore(Collections.singletonList("\u00A77Cancel current action and return to previous menu.")); + meta.setDisplayName("§cCancel"); + meta.setLore(Collections.singletonList("§7Cancel current action and return to previous menu.")); CANCEL_ITEM.setItemMeta(meta); CONFIRM_ITEM = new ItemStack(Material.GREEN_TERRACOTTA); meta = CONFIRM_ITEM.getItemMeta(); assert meta != null; - meta.setDisplayName("\u00A7aConfirm"); - meta.setLore(Collections.singletonList("\u00A77Confirm current action.")); + meta.setDisplayName("§aConfirm"); + meta.setLore(Collections.singletonList("§7Confirm current action.")); CONFIRM_ITEM.setItemMeta(meta); CONFIRM_PERMANENT_ITEM = new ItemStack(Material.GREEN_TERRACOTTA); meta = CONFIRM_PERMANENT_ITEM.getItemMeta(); assert meta != null; - meta.setDisplayName("\u00A7aConfirm"); - meta.setLore(Arrays.asList("\u00A77Confirm current action.", - "\u00A74Cation: This action can't be canceled.")); + meta.setDisplayName("§aConfirm"); + meta.setLore(Arrays.asList("§7Confirm current action.", + "§4Cation: This action can't be canceled.")); CONFIRM_PERMANENT_ITEM.setItemMeta(meta); } 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 new file mode 100644 index 0000000..707a218 --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/update/PluginSetDefault.java @@ -0,0 +1,98 @@ +package xyz.alexcrea.cuanvil.update; + +import io.delilaheve.CustomAnvil; +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; + +import static io.delilaheve.util.ConfigOptions.*; +import static xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.*; + +public class PluginSetDefault { + + public static void reAddMissingDefault() { + FileConfiguration config = ConfigHolder.DEFAULT_CONFIG.getConfig(); + + 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); + nbSet += trySetDefault(config, REPLACE_TOO_EXPENSIVE, DEFAULT_REPLACE_TOO_EXPENSIVE); + nbSet += trySetDefault(config, ITEM_REPAIR_COST, DEFAULT_ITEM_REPAIR_COST); + nbSet += trySetDefault(config, UNIT_REPAIR_COST, DEFAULT_UNIT_REPAIR_COST); + nbSet += trySetDefault(config, ITEM_RENAME_COST, DEFAULT_ITEM_RENAME_COST); + nbSet += trySetDefault(config, SACRIFICE_ILLEGAL_COST, DEFAULT_SACRIFICE_ILLEGAL_COST); + nbSet += trySetDefault(config, ConfigOptions.ALLOW_COLOR_CODE, ConfigOptions.DEFAULT_ALLOW_COLOR_CODE); + 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, PER_COLOR_CODE_PERMISSION, DEFAULT_PER_COLOR_CODE_PERMISSION); + + // Lore Edit defaults + for (@NotNull LoreEditType value : LoreEditType.values()) { + String path = value.getRootPath() + "."; + + nbSet += trySetDefault(config, path + IS_ENABLED, DEFAULT_IS_ENABLED); + nbSet += trySetDefault(config, path + FIXED_COST, DEFAULT_FIXED_COST); + + nbSet += trySetDefault(config, path + DO_CONSUME, DEFAULT_DO_CONSUME); + if (value.isMultiLine()) { + nbSet += trySetDefault(config, path + PER_LINE_COST, DEFAULT_PER_LINE_COST); + } + if (value.isAppend()) { + nbSet += trySetDefault(config, path + LoreEditConfigUtil.ALLOW_COLOR_CODE, LoreEditConfigUtil.DEFAULT_ALLOW_COLOR_CODE); + 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_COST, DEFAULT_REMOVE_COLOR_COST); + } + } + + nbSet += trySetDefault(config, BOOK_PERMISSION_NEEDED, DEFAULT_BOOK_PERMISSION_NEEDED); + nbSet += trySetDefault(config, PAPER_PERMISSION_NEEDED, DEFAULT_PAPER_PERMISSION_NEEDED); + + 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); + } + + } + + private static int trySetDefault(@NotNull FileConfiguration config, @NotNull String path, @NotNull String value) { + if (config.isSet(path)) return 0; + + config.set(path, value); + return 1; + } + + private static int trySetDefault(@NotNull FileConfiguration config, @NotNull String path, int value) { + if (config.isSet(path)) return 0; + + config.set(path, value); + return 1; + } + + private static int trySetDefault(@NotNull FileConfiguration config, @NotNull String path, boolean value) { + if (config.isSet(path)) return 0; + + config.set(path, value); + return 1; + } + + +} 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 6448ced..2907fef 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/update/UpdateUtils.java +++ b/src/main/java/xyz/alexcrea/cuanvil/update/UpdateUtils.java @@ -1,5 +1,6 @@ package xyz.alexcrea.cuanvil.update; +import org.bukkit.Bukkit; import org.bukkit.configuration.file.FileConfiguration; import java.util.ArrayList; @@ -7,23 +8,29 @@ import java.util.Arrays; import java.util.List; public class UpdateUtils { - public final static String MINECRAFT_VERSION_PATH = "lowMinecraftVersion"; + public static final String MINECRAFT_VERSION_PATH = "lowMinecraftVersion"; - 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 Version currentMinecraftVersion() { + String versionString = Bukkit.getServer().getBukkitVersion().split("-")[0]; + return Version.fromString(versionString); } - 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 dca039a..0000000 --- a/src/main/java/xyz/alexcrea/cuanvil/update/Update_1_21.java +++ /dev/null @@ -1,98 +0,0 @@ -package xyz.alexcrea.cuanvil.update; - -import io.delilaheve.CustomAnvil; -import org.bukkit.Bukkit; -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 { - - 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){ - int[] versionParts = UpdateUtils.readVersionFromString(oldVersion); - - // Test 1.21 - if((versionParts[0] >= 1) && (versionParts[1] >= 21)){ - return; - } - } - - String versionString = Bukkit.getServer().getBukkitVersion().split("-")[0]; - int[] versionParts = UpdateUtils.readVersionFromString(versionString); - - // Test 1.21 - if((versionParts[0] >= 1) && (versionParts[1] >= 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(); - - // 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", "density"); - addToStringList(conflictConfig, "restriction_density.notAffectedGroups", "mace"); - - addToStringList(conflictConfig, "restriction_breach.enchantments", "breach"); - addToStringList(conflictConfig, "restriction_breach.notAffectedGroups", "mace"); - - addToStringList(conflictConfig, "restriction_wind_burst.enchantments", "wind_burst"); - addToStringList(conflictConfig, "restriction_wind_burst.notAffectedGroups", "mace"); - - // 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", "density", "breach", "smite", "bane_of_arthropods"); - conflictConfig.set("mace_enchant_conflict.maxEnchantmentBeforeConflict", 1); - - // Add level limit - baseConfig.set("enchant_limits.density", 5); - baseConfig.set("enchant_limits.breach", 4); - baseConfig.set("enchant_limits.wind_burst", 3); - - // Add enchant values - baseConfig.set("enchant_values.density.item", 1); - baseConfig.set("enchant_values.density.book", 1); - - baseConfig.set("enchant_values.breach.item", 4); - baseConfig.set("enchant_values.breach.book", 2); - - baseConfig.set("enchant_values.wind_burst.item", 4); - baseConfig.set("enchant_values.wind_burst.book", 2); - - // 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); - - // 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 new file mode 100644 index 0000000..a49fbdd --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/update/Version.java @@ -0,0 +1,62 @@ +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) { + + public Version(int major, int minor){ + this(major, minor, 0); + } + public Version(int major){ + this(major, 0, 0); + } + + 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++) { + try { + versionParts[i] = Integer.parseInt(partialVersion[i]); + } catch (NumberFormatException e) { + break; + } + } + return new Version(versionParts[0], versionParts[1], versionParts[2]); + } + + public boolean greaterThan(@Nonnull Version other){ + return this.major > other.major || (this.major == other.major && + (this.minor > other.minor || (this.minor == other.minor && + this.patch > other.patch))); + } + + public boolean greaterEqual(@Nonnull Version other){ + return this.major > other.major || (this.major == other.major && + (this.minor > other.minor || (this.minor == other.minor && + this.patch >= other.patch))); + } + + public boolean lesserThan(@Nonnull Version other){ + return this.major < other.major || (this.major == other.major && + (this.minor < other.minor || (this.minor == other.minor && + this.patch < other.patch))); + } + + public boolean lesserEqual(@Nonnull Version other){ + return this.major < other.major || (this.major == other.major && + (this.minor < other.minor || (this.minor == other.minor && + 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 new file mode 100644 index 0000000..a4078de --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_6_2.java @@ -0,0 +1,55 @@ +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_6_2 { + + private static final String[] toUpdate = new String[] {"restriction_density", "restriction_breach", "restriction_wind_burst"}; + + public static void handleUpdate(@Nonnull Set toSave) { + FileConfiguration config = ConfigHolder.CONFLICT_HOLDER.getConfig(); + + boolean conflictUpdated = false; + for (String restriction : toUpdate) { + if(!config.isConfigurationSection(restriction)) continue; + String path = restriction + ".notAffectedGroups"; + + boolean contained = false; + for (String value : config.getStringList(path)) { + if(value.equalsIgnoreCase("enchanted_book")) { + contained = true; + break; + } + } + + if(!contained){ + addAbsentToList(config, path, "enchanted_book"); + conflictUpdated = true; + } + } + + if(conflictUpdated){ + toSave.add(ConfigHolder.CONFLICT_HOLDER); + + // May not be the most efficient for later revision, maybe move to PluginUpdates + ConfigHolder.CONFLICT_HOLDER.reload(); + } + + // Then we add the unit repair + config = ConfigHolder.UNIT_REPAIR_HOLDER.getConfig(); + String unitRepairPath = "breeze_rod.mace"; + if(!config.isConfigurationSection(unitRepairPath)){ + config.set(unitRepairPath, 0.25); + + toSave.add(ConfigHolder.UNIT_REPAIR_HOLDER); + } + + } + +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_6_7.java b/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_6_7.java new file mode 100644 index 0000000..ee544dc --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_6_7.java @@ -0,0 +1,25 @@ +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; + +public class PUpdate_1_6_7 { + + public static void handleUpdate(@Nonnull Set toSave) { + FileConfiguration config = ConfigHolder.DEFAULT_CONFIG.getConfig(); + + // We fix the density enchantment + String value = config.getString("enchant_values.minecraft:density.item"); + if(value == null) value = config.getString("enchant_values.density.item"); + + if(value == null || "1".equalsIgnoreCase(value)){ + config.set("enchant_values.minecraft:density.item", 2); + + toSave.add(ConfigHolder.DEFAULT_CONFIG); + } + } + +} 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 new file mode 100644 index 0000000..81ce1aa --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_8_0.java @@ -0,0 +1,66 @@ +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 javax.annotation.Nonnull; +import java.util.EnumMap; +import java.util.Set; + +public class PUpdate_1_8_0 { + + private static final String WORK_PENALTY_TYPE = "work_penalty_type"; + + public static void handleUpdate(@Nonnull Set toSave) { + FileConfiguration config = ConfigHolder.DEFAULT_CONFIG.getConfig(); + + // We migrate the work penalty type if it exists + String penaltyTypeValue = config.getString(WORK_PENALTY_TYPE); + if (penaltyTypeValue == null) return; + + EnumMap partEnum; + partEnum = new EnumMap<>(ConfigOptions.INSTANCE.getWorkPenaltyType().getPartMap()); + + boolean keepIncrease; + boolean keepAdditive; + + switch (penaltyTypeValue.toLowerCase()) { + case "add_only": + keepIncrease = false; + keepAdditive = true; + break; + case "increase_only": + keepIncrease = true; + keepAdditive = false; + break; + case "disabled": + keepIncrease = false; + keepAdditive = false; + break; + default: + keepIncrease = true; + keepAdditive = true; + } + + for (AnvilUseType type : partEnum.keySet()) { + WorkPenaltyType.WorkPenaltyPart part = partEnum.get(type); + part = new WorkPenaltyType.WorkPenaltyPart( + keepIncrease & part.penaltyIncrease(), + keepAdditive & part.penaltyAdditive(), + part.exclusivePenaltyIncrease(), + part.exclusivePenaltyAdditive()); + partEnum.replace(type, part); + } + + if(WorkPenaltyTypeSettingGui.saveWorkPenalty(partEnum)){ + config.set(WORK_PENALTY_TYPE, null); + } + + toSave.add(ConfigHolder.DEFAULT_CONFIG); + } + +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/util/LazyValue.java b/src/main/java/xyz/alexcrea/cuanvil/util/LazyValue.java new file mode 100644 index 0000000..3ae8fdd --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/util/LazyValue.java @@ -0,0 +1,36 @@ +package xyz.alexcrea.cuanvil.util; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Supplier; + +public class LazyValue { + + private final Supplier valueSupplier; + private T storedValue; + + public LazyValue(Supplier valueSupplier) { + this.valueSupplier = valueSupplier; + this.storedValue = null; + } + + @Nullable + public T getStored(){ + return storedValue; + } + + @NotNull + public T get(){ + if (storedValue != null) return storedValue; + + synchronized(this) { + if(storedValue == null) { + storedValue = valueSupplier.get(); + } + } + + return storedValue; + } + +} diff --git a/src/main/kotlin/io/delilaheve/AnvilEventListener.kt b/src/main/kotlin/io/delilaheve/AnvilEventListener.kt deleted file mode 100644 index f6f3303..0000000 --- a/src/main/kotlin/io/delilaheve/AnvilEventListener.kt +++ /dev/null @@ -1,594 +0,0 @@ -package io.delilaheve - -import io.delilaheve.util.ConfigOptions -import io.delilaheve.util.EnchantmentUtil.combineWith -import io.delilaheve.util.EnchantmentUtil.enchantmentName -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.GameMode -import org.bukkit.Material -import org.bukkit.entity.Player -import org.bukkit.event.Event -import org.bukkit.event.EventHandler -import org.bukkit.event.EventPriority.HIGHEST -import org.bukkit.event.Listener -import org.bukkit.event.inventory.ClickType -import org.bukkit.event.inventory.InventoryClickEvent -import org.bukkit.event.inventory.InventoryCloseEvent -import org.bukkit.event.inventory.PrepareAnvilEvent -import org.bukkit.inventory.AnvilInventory -import org.bukkit.inventory.InventoryView.Property.REPAIR_COST -import org.bukkit.inventory.ItemStack -import org.bukkit.inventory.meta.Repairable -import xyz.alexcrea.cuanvil.config.ConfigHolder -import xyz.alexcrea.cuanvil.group.ConflictType -import xyz.alexcrea.cuanvil.dependency.protocolib.PacketManager -import xyz.alexcrea.cuanvil.recipe.AnvilCustomRecipe -import xyz.alexcrea.cuanvil.util.UnitRepairUtil.getRepair -import kotlin.math.min - - -/** - * Listener for anvil events - */ -class AnvilEventListener(private val packetManager: PacketManager) : Listener { - - companion object { - // Anvil's output slot - private const val ANVIL_INPUT_LEFT = 0 - private const val ANVIL_INPUT_RIGHT = 1 - private const val ANVIL_OUTPUT_SLOT = 2 - - // static slot container - private val NO_SLOT = SlotContainer(SlotType.NO_SLOT, 0) - private val CURSOR_SLOT = SlotContainer(SlotType.CURSOR, 0) - } - - /** - * Event handler logic for when an anvil contains items to be combined - */ - @EventHandler(priority = HIGHEST) - fun anvilCombineCheck(event: PrepareAnvilEvent) { - val inventory = event.inventory - val first = inventory.getItem(ANVIL_INPUT_LEFT) ?: return - val second = inventory.getItem(ANVIL_INPUT_RIGHT) - - // Should find player - val player = event.view.player - if (!player.hasPermission(CustomAnvil.affectedByPluginPermission)) return - - // Test custom recipe - val recipe = getCustomRecipe(first, second) - CustomAnvil.verboseLog("custom recipe not null? ${recipe != null}") - if(recipe != null){ - val amount = getCustomRecipeAmount(recipe, first, second) - - val resultItem: ItemStack = recipe.resultItem!!.clone() - resultItem.amount *= amount - - event.result = resultItem - handleAnvilXp(inventory, event, recipe.xpCostPerCraft * amount, true) - - return - } - - // Test rename lonely item - if (second == null) { - val resultItem = first.clone() - var anvilCost = handleRename(resultItem, inventory) - - // Test/stop if nothing changed. - if (first == resultItem) { - CustomAnvil.log("no right item, But input is same as output") - event.result = null - return - } - // We don't manually set item here as vanilla do it (renaming) - //event.result = null - - anvilCost += calculatePenalty(first, null, resultItem) - - handleAnvilXp(inventory, event, anvilCost) - return - } - - // Test for merge - if (first.canMergeWith(second)) { - val newEnchants = first.findEnchantments() - .combineWith(second.findEnchantments(), first.type, player) - val resultItem = first.clone() - resultItem.setEnchantmentsUnsafe(newEnchants) - - // Calculate enchantment cost - var anvilCost = 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 += calculatePenalty(first, second, resultItem) - // Calculate rename cost - anvilCost += handleRename(resultItem, inventory) - - // Finally, we set result - event.result = resultItem - - handleAnvilXp(inventory, event, anvilCost) - return - } - - // Test for unit repair - val unitRepairAmount = first.getRepair(second) - if (unitRepairAmount != null) { - val resultItem = first.clone() - var anvilCost = handleRename(resultItem, inventory) - - 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 += calculatePenalty(first, null, resultItem) - - // Test/stop if nothing changed. - if (first == resultItem) { - CustomAnvil.log("unit repair, But input is same as output") - event.result = null - return - } - event.result = resultItem - - handleAnvilXp(inventory, event, anvilCost) - } else { - CustomAnvil.log("no anvil fuse type found") - event.result = null - } - - } - - private fun handleRename(resultItem: ItemStack, inventory: AnvilInventory): Int { - // Rename item and add renaming cost - resultItem.itemMeta?.let { - val displayName = ChatColor.stripColor(it.displayName) - val inventoryName = ChatColor.stripColor(inventory.renameText) - if (!displayName.contentEquals(inventoryName)) { - it.setDisplayName(inventory.renameText) - resultItem.itemMeta = it - return ConfigOptions.itemRenameCost - } - } - return 0 - } - - /** - * Event handler logic for when a player is trying to pull an item out of the anvil - */ - @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 - if (event.rawSlot != ANVIL_OUTPUT_SLOT) { - return - } - val output = inventory.getItem(ANVIL_OUTPUT_SLOT) ?: return - val leftItem = inventory.getItem(ANVIL_INPUT_LEFT) ?: return - val rightItem = inventory.getItem(ANVIL_INPUT_RIGHT) - - // Test custom recipe - val recipe = getCustomRecipe(leftItem, rightItem) - if(recipe != null){ - event.result = Event.Result.ALLOW - onCustomCraft( - event, recipe, player, - leftItem, rightItem, output, inventory) - return - } - - val canMerge = leftItem.canMergeWith(rightItem) - val unitRepairResult = leftItem.getRepair(rightItem) - val allowed = (rightItem == null) - || (canMerge) - || (unitRepairResult != null) - - // True if there was no change or not allowed - if ((output == inventory.getItem(ANVIL_INPUT_LEFT)) - || !allowed - ) { - event.result = Event.Result.DENY - return - } - if (rightItem == null) { - event.result = Event.Result.ALLOW - return - } - if (canMerge) { - event.result = Event.Result.ALLOW - } else if (unitRepairResult != null) { - onUnitRepairExtract( - leftItem, rightItem, output, - unitRepairResult, event, player, inventory - ) - - return - } - } - - private fun onCustomCraft(event: InventoryClickEvent, - recipe: AnvilCustomRecipe, - player: Player, - leftItem: ItemStack, - rightItem: ItemStack?, - output: ItemStack, - inventory: AnvilInventory) { - event.result = Event.Result.DENY - - if(recipe.leftItem == null) return // in case it changed - - val amount = getCustomRecipeAmount(recipe, leftItem, rightItem) - val xpCost = amount * recipe.xpCostPerCraft - - if ((player.gameMode != GameMode.CREATIVE) && (player.level < xpCost)) return - - // We give the item manually - // But first we check if we should give the item - val slotDestination = getActionSlot(event, player) - if (slotDestination.type == SlotType.NO_SLOT) return - - // If not creative middle click... - if (event.click != ClickType.MIDDLE) { - // We remove what should be removed - leftItem.amount -= amount * recipe.leftItem!!.amount - inventory.setItem(ANVIL_INPUT_LEFT, leftItem) - - if(rightItem != null){ - if(recipe.rightItem == null) return // in case it changed - - rightItem.amount -= amount * recipe.rightItem!!.amount - inventory.setItem(ANVIL_INPUT_RIGHT, rightItem) - } - player.level -= amount - - // Then we try to find the new values for the anvil - val newAmount = getCustomRecipeAmount(recipe, leftItem, rightItem) - - CustomAnvil.verboseLog("new amount is $newAmount") - if(newAmount <= 0 || recipe.exactCount){ - inventory.setItem(ANVIL_OUTPUT_SLOT, null) - }else{ - val resultItem: ItemStack = recipe.resultItem!!.clone() - resultItem.amount *= newAmount - - val newXp = newAmount * newAmount - - inventory.repairCost = newXp - event.view.setProperty(REPAIR_COST, newXp) - - inventory.setItem(ANVIL_OUTPUT_SLOT, resultItem) - - player.updateInventory() - } - } - - // Finally, we add the item to the player - if (slotDestination.type == SlotType.CURSOR) { - player.setItemOnCursor(output) - } else {// We assume SlotType == SlotType.INVENTORY - player.inventory.setItem(slotDestination.slot, output) - } - - - } - - 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 - ) - - // To avoid vanilla, we cancel the event for unit repair - event.result = Event.Result.DENY - event.isCancelled = true - // And we give the item manually - // But first we check if we should give the item - val slotDestination = getActionSlot(event, player) - if (slotDestination.type == SlotType.NO_SLOT) return - - // Test repair cost - var repairCost = 0 - if (player.gameMode != GameMode.CREATIVE) { - // Get repairCost - leftItem.itemMeta?.let { leftMeta -> - val leftName = leftMeta.displayName - output.itemMeta?.let { - if (!leftName.contentEquals(it.displayName)) { - repairCost += ConfigOptions.itemRenameCost - } - } - } - - repairCost += calculatePenalty(leftItem, null, resultCopy) - repairCost += resultAmount * ConfigOptions.unitRepairCost - - if ( - !ConfigOptions.doRemoveCostLimit && - ConfigOptions.doCapCost) { - - repairCost = min(repairCost, ConfigOptions.maxAnvilCost) - } - - if ((inventory.maximumRepairCost <= repairCost) - || (player.level < repairCost) - ) return - } - // If not creative middle click... - if (event.click != ClickType.MIDDLE) { - // We remove what should be removed - inventory.setItem(ANVIL_INPUT_LEFT, null) - rightItem.amount -= resultAmount - inventory.setItem(ANVIL_INPUT_RIGHT, rightItem) - inventory.setItem(ANVIL_OUTPUT_SLOT, null) - player.level -= repairCost - } - - // Finally, we add the item to the player - if (slotDestination.type == SlotType.CURSOR) { - player.setItemOnCursor(output) - } else {// We assume SlotType == SlotType.INVENTORY - player.inventory.setItem(slotDestination.slot, output) - } - } - - /** - * Get the destination slot or "NO_SLOT" slot container if there is no slot available - */ - private fun getActionSlot(event: InventoryClickEvent, player: Player): SlotContainer { - if (event.isShiftClick) { - val inventory = player.inventory - val firstEmpty = inventory.firstEmpty() - if (firstEmpty == -1) { - return NO_SLOT - } - //check hotbare full - var slotIndex = 8 - while (slotIndex >= 0 && ((inventory.getItem(slotIndex)?.type ?: Material.AIR) != Material.AIR)) { - slotIndex-- - } - if (slotIndex >= 0) { - return SlotContainer(SlotType.INVENTORY, slotIndex) - } - slotIndex = 35 //4*9 - 1 (max of player inventory) - while (slotIndex >= 9 && ((inventory.getItem(slotIndex)?.type ?: Material.AIR) != Material.AIR)) { - slotIndex-- - } - if (slotIndex < 9) { - return NO_SLOT - } - return SlotContainer(SlotType.INVENTORY, slotIndex) - } else { - if (player.itemOnCursor.type != Material.AIR) { - return NO_SLOT - } - return CURSOR_SLOT - } - } - - /** - * Function to calculate work penalty of anvil work - * Also change result work penalty - */ - private fun calculatePenalty(left: ItemStack, right: ItemStack?, result: ItemStack): Int { - // Extracted From https://minecraft.fandom.com/wiki/Anvil_mechanics#Enchantment_equation - // Calculate work penalty - val leftPenalty = (left.itemMeta as? Repairable)?.repairCost ?: 0 - val rightPenalty = - if (right == null) { - 0 - } else { - (right.itemMeta as? Repairable)?.repairCost ?: 0 - } - - // Try to set work penalty for the result item - result.itemMeta?.let { - (it as? Repairable)?.repairCost = leftPenalty * 2 + 1 - result.itemMeta = it - } - - CustomAnvil.log( - "Calculated penalty: " + - "leftPenalty: $leftPenalty, " + - "rightPenalty: $rightPenalty, " + - "result penalty: ${(result.itemMeta as? Repairable)?.repairCost ?: "none"}" - ) - - return leftPenalty + rightPenalty - } - - /** - * Function to calculate right enchantment values - * it include enchantment placed on final item and conflicting enchantment - */ - private fun getRightValues(right: ItemStack, result: ItemStack): Int { - // Calculate right value and illegal enchant penalty - var illegalPenalty = 0 - var rightValue = 0 - - val rightIsFormBook = right.isEnchantedBook() - val resultEnchs = result.findEnchantments() - val resultEnchsKeys = HashSet(resultEnchs.keys) - - for (enchantment in right.findEnchantments()) { - // count enchant as illegal enchant if it conflicts with another enchant or not in result - if ((enchantment.key !in resultEnchsKeys)) { - resultEnchsKeys.add(enchantment.key) - val conflictType = ConfigHolder.CONFLICT_HOLDER.conflictManager.isConflicting( - resultEnchsKeys, - result.type, - enchantment.key - ) - resultEnchsKeys.remove(enchantment.key) - - if (ConflictType.BIG_CONFLICT == conflictType) { - illegalPenalty += ConfigOptions.sacrificeIllegalCost - CustomAnvil.verboseLog("Big conflict. Adding illegal price penalty") - } - continue - } - // We know "enchantment.key in resultEnchs" true - val resultLevel = resultEnchs[enchantment.key]!! - - val enchantmentMultiplier = ConfigOptions.enchantmentValue(enchantment.key, rightIsFormBook) - val value = resultLevel * enchantmentMultiplier - CustomAnvil.log("Value for ${enchantment.key.enchantmentName} level ${enchantment.value} is $value") - rightValue += value - - } - CustomAnvil.log( - "Calculated right values: " + - "rightValue: $rightValue, " + - "illegalPenalty: $illegalPenalty" - ) - - return rightValue + illegalPenalty - } - - private fun getCustomRecipe ( - leftItem: ItemStack, - rightItem: ItemStack?) : AnvilCustomRecipe? { - - val recipeList = ConfigHolder.CUSTOM_RECIPE_HOLDER.recipeManager.recipeByMat[leftItem.type] ?: return null - - CustomAnvil.verboseLog("Testing " + recipeList.size+" recipe...") - for (recipe in recipeList) { - if(recipe.testItem(leftItem, rightItem)){ - return recipe - } - } - - return null - } - - private fun getCustomRecipeAmount( - recipe: AnvilCustomRecipe, - leftItem: ItemStack, - rightItem: ItemStack? - ): Int{ - return if(recipe.exactCount) { - if(leftItem.amount != recipe.leftItem!!.amount){ - 0 - }else if(rightItem != null && rightItem.amount != recipe.rightItem!!.amount){ - 0 - }else{ - 1 - } - } - 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 maxLeftAmount = leftItem.amount/recipe.leftItem!!.amount - val maxRightAmount = if(rightItem == null){ maxLeftAmount } else{ rightItem.amount/recipe.rightItem!!.amount } - - CustomAnvil.verboseLog("resultItem: $resultItem, maxResultAmount: $maxResultAmount, maxLeftAmount: $maxLeftAmount, maxRightAmount: $maxRightAmount") - - min(min(maxResultAmount, maxLeftAmount), maxRightAmount) - } - } - - - /** - * Display xp needed for the work on the anvil inventory - */ - private fun handleAnvilXp( - inventory: AnvilInventory, - event: PrepareAnvilEvent, - anvilCost: Int, - ignoreRules: Boolean = false - ) { - // Test repair cost limit - val finalAnvilCost = if ( - !ignoreRules && - !ConfigOptions.doRemoveCostLimit && - ConfigOptions.doCapCost) { - min(anvilCost, ConfigOptions.maxAnvilCost) - } else { - anvilCost - } - - /* Because Minecraft likes to have the final say in the repair cost displayed - * we need to wait for the event to end before overriding it, this ensures that - * we have the final say in the process. */ - CustomAnvil.instance - .server - .scheduler - .runTask(CustomAnvil.instance, Runnable { - inventory.maximumRepairCost = - if (ConfigOptions.doRemoveCostLimit || ignoreRules) - { Int.MAX_VALUE } - else - { ConfigOptions.maxAnvilCost + 1 } - - val player = event.view.player - - inventory.repairCost = finalAnvilCost - event.view.setProperty(REPAIR_COST, finalAnvilCost) - player.openInventory.setProperty(REPAIR_COST, finalAnvilCost) - - if(player is Player){ - if(player.gameMode != GameMode.CREATIVE ){ - val bypassToExpensive = (ConfigOptions.doReplaceTooExpensive) && - (finalAnvilCost >= 40) && - finalAnvilCost < inventory.maximumRepairCost - - packetManager.setInstantBuild(player, bypassToExpensive) - } - - player.updateInventory() - } - }) - } - - @EventHandler - fun onAnvilClose(event: InventoryCloseEvent){ - val player = event.player - if(event.inventory !is AnvilInventory) return - if(player is Player && GameMode.CREATIVE != player.gameMode){ - packetManager.setInstantBuild(player, false) - } - - } - -} - - - - -private class SlotContainer(val type: SlotType, val slot: Int) -private enum class SlotType { - CURSOR, - INVENTORY, - NO_SLOT - -} diff --git a/src/main/kotlin/io/delilaheve/CustomAnvil.kt b/src/main/kotlin/io/delilaheve/CustomAnvil.kt index f2fd784..4d99faf 100644 --- a/src/main/kotlin/io/delilaheve/CustomAnvil.kt +++ b/src/main/kotlin/io/delilaheve/CustomAnvil.kt @@ -4,28 +4,39 @@ import io.delilaheve.util.ConfigOptions import org.bukkit.Bukkit 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.enchant.WrappedEnchantment +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 +import xyz.alexcrea.cuanvil.listener.AnvilCloseListener +import xyz.alexcrea.cuanvil.listener.AnvilResultListener import xyz.alexcrea.cuanvil.listener.ChatEventListener -import xyz.alexcrea.cuanvil.dependency.protocolib.PacketManager -import xyz.alexcrea.cuanvil.update.Update_1_21 -import xyz.alexcrea.cuanvil.util.Metrics +import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener +import xyz.alexcrea.cuanvil.update.ModrinthUpdateChecker +import xyz.alexcrea.cuanvil.update.PluginSetDefault +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 /** * Bukkit/Spigot/Paper plugin to alter anvil feature */ -class CustomAnvil : JavaPlugin() { +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" @@ -39,14 +50,18 @@ 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 @@ -54,10 +69,12 @@ 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) } @@ -72,7 +89,26 @@ 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 } /** @@ -80,9 +116,74 @@ 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 + } - val pluginManager = Bukkit.getPluginManager(); + // 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) { @@ -91,37 +192,79 @@ class CustomAnvil : JavaPlugin() { logger.warning("Please note CustomAnvil is a more recent version of UnsafeEnchantsPlus") } - // Load dependency - DependencyManager.loadDependency() + 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") + } + } - // Register enchantments - WrappedEnchantment.registerEnchantments() + val loader = if(isPaper) "paper" else "spigot" - // Load chat listener + 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() - pluginManager.registerEvents(chatListener, this) + 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) + } + + private fun loadEnchantmentSystem(){ + // Register enchantments + CAEnchantmentRegistry.getInstance().registerBukkit() + DependencyManager.registerEnchantments() + + val enchantReadyEvent = CAEnchantRegistryReadyEvent() + server.pluginManager.callEvent(enchantReadyEvent) // Load config - val success = ConfigHolder.loadConfig() - if (!success) return + if (!ConfigHolder.loadNonDefaultConfig()) { + 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() + // Handle minecraft and plugin updates + UpdateHandler.handleUpdates() + + // Register enchantment of compatible plugin and load configuration change. + DependencyManager.handleCompatibilityConfig() + + // Call config event + val configReadyEvent = CAConfigReadyEvent() + server.pluginManager.callEvent(configReadyEvent) // Load gui constants //TODO maybe something better later MainConfigGui.getInstance().init(DependencyManager.packetManager) GuiSharedConstant.loadConstants() - // Load metrics - Metrics(this, bstatsPluginId) + // Prepare economy if possible + EconomyManager.setupEconomy(this) - // Add commands to reload the plugin - prepareCommand() - - server.pluginManager.registerEvents( - AnvilEventListener(DependencyManager.packetManager), - this - ) + // Finally, re add default we may be missing + PluginSetDefault.reAddMissingDefault() } fun reloadResource( @@ -133,20 +276,34 @@ class CustomAnvil : JavaPlugin() { if (!file.exists()) { saveResource(resourceName, false) } + + return reloadResource(file, hardFailSafe) + } + + // Unlike above function. this function will not clone default from jar. + fun reloadResource( + resourceFile: File, + hardFailSafe: Boolean = true + ): YamlConfiguration? { + // Test if file exist + if (!resourceFile.exists()) { + return null + } + // Load resource val yamlConfig = YamlConfiguration() try { - val configReader = FileReader(file) + val configReader = FileReader(resourceFile) yamlConfig.load(configReader) } catch (test: Exception) { if (hardFailSafe) { // This is important and may impact gameplay if it does not load. // Failsafe is to stop the plugin - logger.severe("Resource $resourceName Could not be load or reload.") + logger.severe("Resource ${resourceFile.path} Could not be load or reload.") logger.severe("Disabling plugin.") Bukkit.getPluginManager().disablePlugin(this) } else { - logger.warning("Resource $resourceName Could not be load or reload.") + logger.warning("Resource ${resourceFile.path} Could not be load or reload.") } return null } @@ -157,8 +314,10 @@ 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 0fefdf2..9dc85f9 100644 --- a/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt +++ b/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt @@ -2,95 +2,141 @@ 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.enchant.WrappedEnchantment +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.dialog.AnvilRenameDialogUtil +import java.math.BigDecimal +import java.util.* /** * Config option accessors */ object ConfigOptions { - // Path for limiting anvil cost + // ---------------------- + // 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" - - // Path for max anvil cost value const val MAX_ANVIL_COST = "limit_repair_value" - - // Path for removing anvil cost limits const val REMOVE_ANVIL_COST_LIMIT = "remove_repair_limit" - // Path for removing too expensive when unused const val REPLACE_TOO_EXPENSIVE = "replace_too_expensive" - // Path for level cost on item repair const val ITEM_REPAIR_COST = "item_repair_cost" - - // Path for level cost on unit repair const val UNIT_REPAIR_COST = "unit_repair_cost" - // Path for level cost on item renaming const val ITEM_RENAME_COST = "item_rename_cost" - // Path for level cost on illegal enchantment on sacrifice const val SACRIFICE_ILLEGAL_COST = "sacrifice_illegal_enchant_cost" + const val ADD_BOOK_ENCHANTMENT_AS_STORED_ENCHANTMENT = "add_book_enchantment_as_stored_enchantment" - // Path for default enchantment limits - private const val DEFAULT_LIMIT_PATH = "default_limit" + // 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" + const val WORK_PENALTY_ADDITIVE = "shared_additive" + const val EXCLUSIVE_WORK_PENALTY_INCREASE = "exclusive_increase" + const val EXCLUSIVE_WORK_PENALTY_ADDITIVE = "exclusive_additive" + + // 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" - // Root path for enchantment limits const val ENCHANT_LIMIT_ROOT = "enchant_limits" - - - // Root path for enchantment values 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" + // Debug flag + const val DEBUG_LOGGING = "debug_log" + const val VERBOSE_DEBUG_LOGGING = "debug_log_verbose" - // Debug logging toggle path - private const val DEBUG_LOGGING = "debug_log" + // ---------------------- + // Default config values + // ---------------------- - // Debug verbose logging toggle path - private const val VERBOSE_DEBUG_LOGGING = "debug_log_verbose" - - - // Default value for limiting repair cost const val DEFAULT_CAP_ANVIL_COST = false - - // Default value for repair cost limit const val DEFAULT_MAX_ANVIL_COST = 39 - - // Default for removing repair cost limits const val DEFAULT_REMOVE_ANVIL_COST_LIMIT = false - // Default for removing repair cost limits const val DEFAULT_REPLACE_TOO_EXPENSIVE = false - // Default value for level cost on item repair const val DEFAULT_ITEM_REPAIR_COST = 2 - - // Default value for level cost per unit repair const val DEFAULT_UNIT_REPAIR_COST = 1 - // Default value for level cost on item renaming const val DEFAULT_ITEM_RENAME_COST = 1 - // Default value for level cost on illegal enchantment on sacrifice const val DEFAULT_SACRIFICE_ILLEGAL_COST = 1 + const val DEFAULT_ADD_BOOK_ENCHANTMENT_AS_STORED_ENCHANTMENT = false + const val DEFAULT_ENCHANT_COUNT_LIMIT = -1 - // Default value for enchantment limits - private const val DEFAULT_ENCHANT_LIMIT = 5 + // 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 - // Default value for debug logging + 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 - - // Default value for debug logging 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 + // ------------- // Valid range for repair cost limit @JvmField @@ -108,14 +154,39 @@ object ConfigOptions { @JvmField val SACRIFICE_ILLEGAL_COST_RANGE = 0..1000 - // Valid range for an enchantment limit + // Valid range for color use cost @JvmField - val ENCHANT_LIMIT_RANGE = 1..255 + val USE_OF_COLOR_COST_RANGE = 0..1000 + @JvmField + 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 + // -------------- // Default value for an enchantment multiplier private const val DEFAULT_ENCHANT_VALUE = 0 + // 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 + // ------------- + /** * Whether to cap anvil costs */ @@ -207,13 +278,157 @@ object ConfigOptions { } /** - * Default enchantment limit + * Consider book enchantment as book stored enchantment */ - private val defaultEnchantLimit: Int + 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 + */ + val allowColorCode: Boolean get() { return ConfigHolder.DEFAULT_CONFIG .config - .getInt(DEFAULT_LIMIT_PATH, DEFAULT_ENCHANT_LIMIT) + .getBoolean(ALLOW_COLOR_CODE, DEFAULT_ALLOW_COLOR_CODE) + } + + /** + * Allow usage of hexadecimal color + */ + val allowHexadecimalColor: Boolean + get() { + return ConfigHolder.DEFAULT_CONFIG + .config + .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 || allowMinimessage + } + + /** + * If players need a permission to use color + */ + val permissionNeededForColor: Boolean + get() { + return ConfigHolder.DEFAULT_CONFIG + .config + .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 + */ + val useOfColorCost: Int + get() { + return ConfigHolder.DEFAULT_CONFIG + .config + .getInt(USE_OF_COLOR_COST, DEFAULT_USE_OF_COLOR_COST) + .takeIf { it in USE_OF_COLOR_COST_RANGE } + ?: DEFAULT_USE_OF_COLOR_COST + } + + /** + * How work penalties should work + */ + val workPenaltyType: WorkPenaltyType + get() { + val penaltyMap = EnumMap(AnvilUseType::class.java) + + for (type in AnvilUseType.entries) { + penaltyMap[type] = workPenaltyPart(type) + } + + return WorkPenaltyType(penaltyMap) + } + + /** + * How work penalty should work + */ + fun workPenaltyPart(type: AnvilUseType): WorkPenaltyPart { + val config = ConfigHolder.DEFAULT_CONFIG.config + + // Find values + val defaultPenalty = type.defaultPenalty + val section = config.getConfigurationSection(type.path) ?: return defaultPenalty + + val penaltyIncrease = section.getBoolean(WORK_PENALTY_INCREASE, defaultPenalty.penaltyIncrease) + val penaltyAdditive = section.getBoolean(WORK_PENALTY_ADDITIVE, defaultPenalty.penaltyAdditive) + val exclusivePenaltyIncrease = + section.getBoolean(EXCLUSIVE_WORK_PENALTY_INCREASE, defaultPenalty.exclusivePenaltyIncrease) + val exclusivePenaltyAdditive = + section.getBoolean(EXCLUSIVE_WORK_PENALTY_ADDITIVE, defaultPenalty.exclusivePenaltyAdditive) + + return WorkPenaltyPart(penaltyIncrease, penaltyAdditive, exclusivePenaltyIncrease, exclusivePenaltyAdditive) + } + + /** + * Get material enchantment count limit + * + * @return the current enchantment limit. -1 if none + */ + 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(ENCHANT_COUNT_LIMIT_DEFAULT, DEFAULT_ENCHANT_COUNT_LIMIT) + .takeIf { it in ENCHANT_COUNT_LIMIT_RANGE } + ?: DEFAULT_ENCHANT_COUNT_LIMIT } /** @@ -236,36 +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: WrappedEnchantment): Int { - return enchantLimit(enchantment.enchantmentName) + 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 >= 0) return limit + + // Test legacy (name only) + limit = enchantLimit(enchantment.enchantmentName) + if (limit >= 0) return limit + + // Default to negative + return -1 } /** * Get the given [enchantmentName]'s limit */ private fun enchantLimit(enchantmentName: String): Int { - val default = getDefaultLevel(enchantmentName) val path = "${ENCHANT_LIMIT_ROOT}.$enchantmentName" - return CustomAnvil.instance - .config - .getInt(path, default) - .takeIf { it in ENCHANT_LIMIT_RANGE } - ?: default - } - - /** - * 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"){ - return enchantLimit("sweeping") - } - return defaultEnchantLimit + return CustomAnvil.instance.config + .getInt(path, -1) } /** @@ -273,10 +542,20 @@ object ConfigOptions { * it's source [isFromBook] */ fun enchantmentValue( - enchantment: WrappedEnchantment, + enchantment: CAEnchantment, isFromBook: Boolean ): Int { - return enchantmentValue(enchantment.enchantmentName, isFromBook) + // Test namespace + var limit = enchantmentValue(enchantment.key.toString(), isFromBook) + if (limit != null) return limit + + // Test legacy (name only) + limit = enchantmentValue(enchantment.enchantmentName, isFromBook) + if (limit != null) return limit + + // get default (and test old legacy if present) + return getDefaultValue(enchantment, isFromBook) + } /** @@ -286,27 +565,115 @@ object ConfigOptions { private fun enchantmentValue( enchantmentName: String, isFromBook: Boolean - ): Int { - val default = getDefaultValue(enchantmentName, isFromBook) - + ): Int? { val typeKey = if (isFromBook) KEY_BOOK else KEY_ITEM val path = "${ENCHANT_VALUES_ROOT}.${enchantmentName}.$typeKey" return CustomAnvil.instance .config - .getInt(path, default) + .getInt(path, DEFAULT_ENCHANT_VALUE - 1) .takeIf { it >= DEFAULT_ENCHANT_VALUE } - ?: DEFAULT_ENCHANT_VALUE } /** * Get default value if enchantment do not exist on config */ - private fun getDefaultValue(enchantmentName: String, // compatibility with 1.20.5. TODO better update system - isFromBook: Boolean) : Int { - if(enchantmentName == "sweeping_edge"){ - return enchantmentValue("sweeping", isFromBook) + private fun getDefaultValue( + enchantment: CAEnchantment, // compatibility with 1.20.5. TODO better update system + isFromBook: Boolean + ): Int { + + val enchantmentName = enchantment.key.toString() + if (enchantmentName == "minecraft:sweeping_edge") { + var limit = enchantmentValue("minecraft:sweeping", isFromBook) + if (limit != null) return limit + + // legacy name + limit = enchantmentValue("sweeping", isFromBook) + if (limit != null) return limit } - return DEFAULT_ENCHANT_VALUE + + val rarity = enchantment.defaultRarity() + return if (isFromBook) + rarity.bookValue + else + rarity.itemValue + } + + /** + * Get the given [enchantmentName]'s level before merge is disabled + * a negative value would mean never disabled + */ + fun maxBeforeMergeDisabled(enchantment: CAEnchantment): Int { + val key = enchantment.key.toString() + var value = maxBeforeMergeDisabled(key) + if (value >= 0) return value + + // Legacy name + val legacy = enchantment.enchantmentName + value = maxBeforeMergeDisabled(legacy) + if (value >= 0) return value + + if (key == "minecraft:sweeping_edge") { + value = maxBeforeMergeDisabled("minecraft:sweeping") + if (value >= 0) return value + + // legacy name of legacy enchantment name + value = maxBeforeMergeDisabled("sweeping") + if (value >= 0) return value + } + + return DEFAULT_MAX_BEFORE_MERGE_DISABLED + } + + /** + * Get the given [enchantmentName]'s level before merge is disabled + * a negative value would mean never disabled + */ + private fun maxBeforeMergeDisabled(enchantmentName: String): Int { + // find if set + val path = "${DISABLE_MERGE_OVER_ROOT}.$enchantmentName" + + return CustomAnvil.instance + .config + .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 9d3f95a..af959f2 100644 --- a/src/main/kotlin/io/delilaheve/util/EnchantmentUtil.kt +++ b/src/main/kotlin/io/delilaheve/util/EnchantmentUtil.kt @@ -1,11 +1,12 @@ package io.delilaheve.util import io.delilaheve.CustomAnvil -import org.bukkit.Material import org.bukkit.entity.HumanEntity +import org.bukkit.inventory.ItemStack import xyz.alexcrea.cuanvil.config.ConfigHolder -import xyz.alexcrea.cuanvil.enchant.WrappedEnchantment +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 @@ -17,70 +18,110 @@ object EnchantmentUtil { /** * Enchantment name without namespace */ - val WrappedEnchantment.enchantmentName: String + val CAEnchantment.enchantmentName: String get() = key.key /** * Combine 2 sets of enchantments according to our configuration */ - fun Map.combineWith( - other: Map, - mat: Material, + fun Map.combineWith( + other: Map, + item: ItemStack, player: HumanEntity - ) = mutableMapOf().apply { + ) = mutableMapOf().apply { putAll(this@combineWith) - other.forEach { (enchantment, level) -> - // Get max level or 255 if player can bypass - val maxLevel = if (player.hasPermission(CustomAnvil.bypassLevelPermission)) - { 255 } else - { ConfigOptions.enchantLimit(enchantment) } + CustomAnvil.verboseLog("Testing merge") + val bypassFuse = player.hasPermission(CustomAnvil.bypassFusePermission) + val bypassLevel = player.hasPermission(CustomAnvil.bypassLevelPermission) + + var maxEnchantCount = ConfigOptions.getEnchantCountLimit(item.customType) + if(maxEnchantCount == null || maxEnchantCount < 0) maxEnchantCount = Int.MAX_VALUE + + 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 $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 - val conflictType = - ConfigHolder.CONFLICT_HOLDER.conflictManager.isConflicting(this.keys, mat, enchantment) - if (!player.hasPermission(CustomAnvil.bypassFusePermission) && - (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) + } + // ... 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 } - // Enchantment already in result list - else { - val oldLevel = this[enchantment]!! // should be true, see the comment above + + 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 - val conflictType = - ConfigHolder.CONFLICT_HOLDER.conflictManager.isConflicting(this.keys, mat, enchantment) - if ((conflictType != ConflictType.NO_CONFLICT) - && !player.hasPermission(CustomAnvil.bypassFusePermission) - ) { - CustomAnvil.verboseLog("Enchantment already in result list, and they are conflicting (${enchantment.key}, conflict: $conflictType)") + 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 } - - // ... 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) - - } - // ... and they're the same level - else { - // try to increase the enchantment level by 1 - var newLevel = oldLevel + 1 - newLevel = max(min(newLevel, maxLevel), oldLevel) - this[enchantment] = newLevel - - } } } + + // 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 6c627ba..25698ad 100644 --- a/src/main/kotlin/io/delilaheve/util/ItemUtil.kt +++ b/src/main/kotlin/io/delilaheve/util/ItemUtil.kt @@ -3,7 +3,10 @@ package io.delilaheve.util import org.bukkit.Material.ENCHANTED_BOOK import org.bukkit.inventory.ItemStack import org.bukkit.inventory.meta.Damageable -import xyz.alexcrea.cuanvil.enchant.WrappedEnchantment +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 @@ -21,21 +24,27 @@ object ItemUtil { /** * Find the enchantment map for this [ItemStack] and return it as a [MutableMap] */ - fun ItemStack.findEnchantments(): MutableMap = WrappedEnchantment.getEnchants(this) + fun ItemStack.findEnchantments(): MutableMap = CAEnchantment.getEnchants(this) /** * Apply an [enchantments] map to this [ItemStack] */ - fun ItemStack.setEnchantmentsUnsafe(enchantments: Map) { - WrappedEnchantment.clearEnchants(this) + fun ItemStack.setEnchantmentsUnsafe(enchantments: Map) { + CAEnchantment.clearEnchants(this) - //TODO maybe faster methode to add vanilla enchantment. maybe move this function to wrapped enchantment enchantments.forEach { (enchantment, level) -> enchantment.addEnchantmentUnsafe(this, level) } } + 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 @@ -55,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 } @@ -91,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/anvil/AnvilUseType.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilUseType.kt new file mode 100644 index 0000000..67782f3 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilUseType.kt @@ -0,0 +1,67 @@ +package xyz.alexcrea.cuanvil.anvil + +import org.bukkit.Material +import xyz.alexcrea.cuanvil.config.WorkPenaltyType +import xyz.alexcrea.cuanvil.util.anvil.AnvilUseTypeUtil + +enum class AnvilUseType( + val typeName: String, val path: String, + val defaultPenalty: WorkPenaltyType.WorkPenaltyPart, + val displayName: String, val displayMat: Material +) { + + RENAME_ONLY( + "rename_only", + WorkPenaltyType.WorkPenaltyPart(false, true), + "Rename Only", Material.NAME_TAG + ), + MERGE( + "merge", + WorkPenaltyType.WorkPenaltyPart(true, true), + "Merge", Material.ANVIL + ), + UNIT_REPAIR( + "unit_repair", + WorkPenaltyType.WorkPenaltyPart(true, true), + "Unit Repair", Material.DIAMOND + ), + CUSTOM_CRAFT( + "custom_craft", + WorkPenaltyType.WorkPenaltyPart(false, false), + "Custom Craft", Material.CRAFTING_TABLE + ), + LORE_EDIT_BOOK_APPEND( + "lore_edit_book_append", "lore_edit.book_and_quil.append", + WorkPenaltyType.WorkPenaltyPart(false, false), + "Book Add", Material.WRITABLE_BOOK + ), + LORE_EDIT_BOOK_REMOVE( + "lore_edit_book_remove", "lore_edit.book_and_quil.remove", + WorkPenaltyType.WorkPenaltyPart(false, false), + "Book Remove", Material.WRITABLE_BOOK + ), + LORE_EDIT_PAPER_APPEND( + "lore_edit_paper_append", "lore_edit.paper.append_line", + WorkPenaltyType.WorkPenaltyPart(false, false), + "Paper Add", Material.WRITABLE_BOOK + ), + LORE_EDIT_PAPER_REMOVE( + "lore_edit_paper_remove", "lore_edit.paper.remove_line", + WorkPenaltyType.WorkPenaltyPart(false, false), + "Paper Remove", Material.WRITABLE_BOOK + ), + ; + + constructor( + typeName: String, + defaultPenalty: WorkPenaltyType.WorkPenaltyPart, + displayName: String, displayMat: Material + ) : + this( + typeName, + AnvilUseTypeUtil.defaultPath(typeName), // stupid util class + defaultPenalty, + displayName, displayMat + ) + +} \ No newline at end of file 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 e05a4f2..fba89b7 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/command/EditConfigExecutor.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/EditConfigExecutor.kt @@ -2,23 +2,44 @@ 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.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 (sender !is HumanEntity) return false + 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("") + sender.sendMessage("§eCurrently you need to edit manually the config or copy from another server (spigot or better)") + sender.sendMessage("§eThen /anvilconfigreload after config file is edited") + 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 151ba6b..ef23e7d 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/command/ReloadExecutor.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/ReloadExecutor.kt @@ -1,15 +1,23 @@ package xyz.alexcrea.cuanvil.command import io.delilaheve.CustomAnvil +import org.bukkit.Bukkit import org.bukkit.command.Command import org.bukkit.command.CommandExecutor import org.bukkit.command.CommandSender +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.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 } @@ -27,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 */ @@ -36,13 +52,23 @@ class ReloadExecutor : CommandExecutor { // Then update all global gui containing value from config BasicConfigGui.getInstance()?.updateGuiValues() - EnchantCostConfigGui.INSTANCE.updateGuiValues() - EnchantLimitConfigGui.INSTANCE.updateGuiValues() + EnchantCostConfigGui.getInstance()?.updateGuiValues() + EnchantLimitConfigGui.getInstance()?.updateGuiValues() - EnchantConflictGui.INSTANCE.reloadValues() - GroupConfigGui.INSTANCE.reloadValues() - UnitRepairConfigGui.INSTANCE.reloadValues() - CustomRecipeConfigGui.INSTANCE.reloadValues() + EnchantConflictGui.getCurrentInstance()?.reloadValues() + GroupConfigGui.getCurrentInstance()?.reloadValues() + UnitRepairConfigGui.getCurrentInstance()?.reloadValues() + CustomRecipeConfigGui.getCurrentInstance()?.reloadValues() + + // handle minecraft version update + UpdateHandler.handleMCVersionUpdate() + + // Handle dependency reload + DependencyManager.handleConfigReload() + + // Call event + val configReadyEvent = CAConfigReadyEvent() + Bukkit.getServer().pluginManager.callEvent(configReadyEvent) return true } catch (e: Exception) { diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt index fa9c994..b730a0e 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt @@ -1,28 +1,331 @@ 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 xyz.alexcrea.cuanvil.dependency.protocolib.NoProtocoLib -import xyz.alexcrea.cuanvil.dependency.protocolib.PacketManager -import xyz.alexcrea.cuanvil.dependency.protocolib.ProtocoLibWrapper +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.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 { + lateinit var scheduler: TaskScheduler lateinit var packetManager: PacketManager - var enchantmentSquaredCompatibility: EnchantmentSquaredDependency? = null; + var externGuiTester: GenericExternGuiTester = GenericExternGuiTester() - fun loadDependency(){ - val pluginManager = Bukkit.getPluginManager(); + var enchantmentSquaredCompatibility: EnchantmentSquaredDependency? = null + var ecoEnchantCompatibility: EcoEnchantDependency? = null + var excellentEnchantsCompatibility: ExcellentEnchantsDependency? = null - // ProtocolLib dependency - packetManager = - if(pluginManager.isPluginEnabled("ProtocolLib")) ProtocoLibWrapper(); - else NoProtocoLib(); + 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 ? + scheduler = if (PlatformUtil.isFolia) { + CustomAnvil.instance.logger.info("Folia detected... Custom Anvil Folia support is experimental. issues are more likely to happens.") + + FoliaScheduler() + } else BukkitScheduler() + + // Packet Manager + val forceProtocolib = ConfigHolder.DEFAULT_CONFIG.config.getBoolean("force_protocolib", false) + packetManager = PacketManagerSelector.selectPacketManager(forceProtocolib) // Enchantment Squared dependency - enchantmentSquaredCompatibility = - if(pluginManager.isPluginEnabled("EnchantsSquared")) EnchantmentSquaredDependency(pluginManager.getPlugin("EnchantsSquared")!!) - else null + if (pluginManager.isPluginEnabled("EnchantsSquared")) { + enchantmentSquaredCompatibility = EnchantmentSquaredDependency(pluginManager.getPlugin("EnchantsSquared")!!) + enchantmentSquaredCompatibility!!.disableAnvilListener() + } + + // EcoEnchants dependency + if (pluginManager.isPluginEnabled("EcoEnchants")) { + ecoEnchantCompatibility = EcoEnchantDependency(pluginManager.getPlugin("EcoEnchants")!!) + ecoEnchantCompatibility!!.disableAnvilListener() + } + + // Excellent Enchants dependency + if (pluginManager.isPluginEnabled("ExcellentEnchants")) { + excellentEnchantsCompatibility = ExcellentEnchantsDependency() + excellentEnchantsCompatibility!!.redirectListeners() + } + + // Disenchantment dependency + if (pluginManager.isPluginEnabled("Disenchantment")) { + disenchantmentCompatibility = DisenchantmentDependency() + disenchantmentCompatibility!!.redirectListeners() + } + + // HavenBags dependency + if (pluginManager.isPluginEnabled("HavenBags")) { + havenBagsCompatibility = HavenBagsDependency() + 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() { + enchantmentSquaredCompatibility?.registerEnchantments() + ecoEnchantCompatibility?.registerEnchantments() + excellentEnchantsCompatibility?.registerEnchantments() + + } + + fun handleConfigReload() { + // Register enchantment of compatible plugin and load configuration change. + handleCompatibilityConfig() + + // Then handle plugin reload + 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) + // called before immutability test + fun earlyTryEventPreAnvilBypass(event: PrepareAnvilEvent, player: HumanEntity): Boolean { + try { + return earlyUnsafeTryEventPreAnvilBypass(event, player) + } catch (e: Exception) { + logExceptionAndClear(event.view.player, event.inventory, e) + return true + } + } + + 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 (!bypass && (disenchantmentCompatibility?.testPrepareAnvil(event, player) == true)) bypass = true + + // Test heaven bags used prepare anvil + if (!bypass && (havenBagsCompatibility?.testPrepareAnvil(event, player) == true)) bypass = true + + // Test excellent enchantments used prepare anvil + if (!bypass && (excellentEnchantsCompatibility?.testPrepareAnvil(event) == true)) bypass = true + + for (genericDependency in genericDependencies) { + if (!bypass && genericDependency.testPrepareAnvil(event)) bypass = true + } + + return bypass + } + + // 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(treatEvent) + return treatEvent.result + } catch (e: Exception) { + logExceptionAndClear(player, inventory, e) + return null + } + } + + private fun unsafeTryTreatAnvilResult(event: CATreatAnvilResult2Event) { + Bukkit.getPluginManager().callEvent(event) + + excellentEnchantsCompatibility?.treatAnvilResult(event) + } + + // Return true if should bypass (either by a dependency or error) + fun tryClickAnvilResultBypass(event: InventoryClickEvent, inventory: AnvilInventory): Boolean { + try { + return unsafeTryClickAnvilResultBypass(event, inventory) + } catch (e: Exception) { + logExceptionAndClear(event.view.player, event.inventory, e) + return true + } + } + + private fun unsafeTryClickAnvilResultBypass(event: InventoryClickEvent, inventory: AnvilInventory): Boolean { + // Run the event + val bypassEvent = CAClickResultBypassEvent(event) + Bukkit.getPluginManager().callEvent(bypassEvent) + + var bypass = bypassEvent.isCancelled + + // Test if disenchantment used event click + 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 + + // 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)) bypass = true + + // Test if in an ax player warp rating gui + if (!bypass && (axPlayerWarpsCompatibility?.testIfGui(event.view.player) == true)) bypass = true + + return bypass + } + + // 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?.componentLore() ?: return ArrayList() + + val lore = ArrayList() + lore.addAll(itemLore) + return lore + } + + fun updateLore(item: ItemStack) { + enchantmentSquaredCompatibility?.updateLore(item) + } + } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/EnchantmentSquaredDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/EnchantmentSquaredDependency.kt deleted file mode 100644 index 2ecdbb9..0000000 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/EnchantmentSquaredDependency.kt +++ /dev/null @@ -1,17 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency - -import me.athlaeos.enchantssquared.managers.CustomEnchantManager -import org.bukkit.plugin.Plugin -import xyz.alexcrea.cuanvil.enchant.WrappedEnchantment -import xyz.alexcrea.cuanvil.enchant.wrapped.EnchantSquaredEnchantment - -class EnchantmentSquaredDependency(private val enchantmentSquaredPlugin: Plugin) { - - fun registerEnchantements(){ - for (enchant in CustomEnchantManager.getInstance().allEnchants.values) { - WrappedEnchantment.register(EnchantSquaredEnchantment(enchant, enchantmentSquaredPlugin)) - } - - } - -} 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/packet/PacketManagerSelector.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt new file mode 100644 index 0000000..d74ef08 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt @@ -0,0 +1,54 @@ +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 { + try { + Class.forName(PAPER_CRAFT_PLAYER_CLASS) + + return PaperPacketManager() + } catch (_: ClassNotFoundException) { + return reobfPacketManager ?: protocolibIfPresent + } + } + } + + private val protocolibIfPresent: PacketManager + get() = + if (Bukkit.getPluginManager().isPluginEnabled("ProtocolLib")) + ProtocoLibWrapper() + else + NoPacketManager() + + // Reobfuscated packet manager for spigot or paper as it remap + private val reobfPacketManager: PacketManagerBase? + get() { + val versionParts = UpdateUtils.currentMinecraftVersion() + if (versionParts.major != 1) return null + + try { + val clazz = Class.forName("xyz.alexcrea.cuanvil.dependency.packet.versions." + + "V${MinecraftVersionUtil.craftbukkitVersion}_PacketManager") + + 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/plugins/DisenchantmentDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/DisenchantmentDependency.kt new file mode 100644 index 0000000..690b384 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/DisenchantmentDependency.kt @@ -0,0 +1,102 @@ +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.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.MetricsUtil.trackError +import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil +import java.util.logging.Level +import kotlin.reflect.KClass + +class DisenchantmentDependency { + + init { + CustomAnvil.instance.logger.info("Disenchantment Detected !") + } + + fun redirectListeners() { + PrepareAnvilEvent.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) + } + } + + 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) { + CustomAnvil.log("Detected pre anvil item extract bypass.") + AnvilXpUtil.setAnvilInvCost(event.inventory, event.view, player, AnvilCost(event.inventory.repairCost)) + return true + } + + ShatterEvent.onEvent(event) + if (event.result != null) { + CustomAnvil.log("Detected pre anvil split enchant bypass.") + AnvilXpUtil.setAnvilInvCost(event.inventory, event.view, player, AnvilCost(event.inventory.repairCost)) + return true + } + + event.result = previousResult + return false + } + + fun testAnvilResult(event: InventoryClickEvent, inventory: AnvilInventory): Boolean { + val previousResultSlot = inventory.getItem(PrepareAnvilListener.ANVIL_OUTPUT_SLOT)?.clone() + + // Test event if change the result + DisenchantClickEvent.onEvent(event) + 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) { + CustomAnvil.log("Detected anvil click split enchant bypass.") + return true + } + + return false + } + + private fun testAnvilInventoryChange(inventory: AnvilInventory, previous: ItemStack?): Boolean { + val currentResult = inventory.getItem(PrepareAnvilListener.ANVIL_OUTPUT_SLOT) + + return currentResult == previous + } + +} diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/EcoEnchantDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/EcoEnchantDependency.kt new file mode 100644 index 0000000..079d570 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/EcoEnchantDependency.kt @@ -0,0 +1,90 @@ +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 +import xyz.alexcrea.cuanvil.api.EnchantmentApi +import xyz.alexcrea.cuanvil.enchant.wrapped.CAEcoEnchant + +class EcoEnchantDependency(private val ecoEnchantPlugin: Plugin) { + + private val isLegacy: Boolean + private val legacyDependency: LegacyEcoEnchantDependency? + + init { + CustomAnvil.instance.logger.info("Eco Enchant Detected !") + + var isLegacy = true + try { + Class.forName("com.willfp.ecoenchants.enchant.EcoEnchants") + isLegacy = false + } catch (_: ClassNotFoundException) { + } + + this.isLegacy = isLegacy; + if (isLegacy) { + this.legacyDependency = LegacyEcoEnchantDependency() + } else { + this.legacyDependency = null + } + + } + + public fun getEcoLevelLimit(): Int { + return (ecoEnchantPlugin as EcoPlugin).configYml.getInt("anvil.enchant-limit").infiniteIfNegative() + } + + fun disableAnvilListener() { + PrepareAnvilEvent.getHandlerList().unregister(this.ecoEnchantPlugin) + } + + private var ecoEnchantOldEnchantments: MutableSet? = null + fun registerEnchantments() { + CustomAnvil.instance.logger.info("Preparing Eco Enchant compatibility...") + + if (isLegacy) { + legacyDependency!!.registerEnchantments(); + return + } + + val enchantments = EcoEnchants.values() + for (ecoEnchant in enchantments) { + EnchantmentApi.unregisterEnchantment(ecoEnchant.enchantment) // As eco enchants is loaded before custom anvil and register enchantment to registry, we need to unregister old "vanilla" enchant. + EnchantmentApi.registerEnchantment(CAEcoEnchant(ecoEnchant)) + } + + ecoEnchantOldEnchantments = HashSet(enchantments) + + CustomAnvil.instance.logger.info("Eco Enchant should now work as expected !") + } + + fun handleConfigReload() { + if (isLegacy) { + legacyDependency!!.handleConfigReload() + return + } + + // Should not happen in known case. + if (this.ecoEnchantOldEnchantments == null) return + + val newEnchantments = EcoEnchants.values() + + // Add new enchantments + for (ecoEnchant in newEnchantments) + if (!this.ecoEnchantOldEnchantments!!.contains(ecoEnchant)) + EnchantmentApi.registerEnchantment(CAEcoEnchant(ecoEnchant)) + + // Remove old enchantments that not now currently used + this.ecoEnchantOldEnchantments!!.removeAll(newEnchantments) + for (oldEnchantment in this.ecoEnchantOldEnchantments!!) { + EnchantmentApi.unregisterEnchantment(oldEnchantment.enchantment) + } + + this.ecoEnchantOldEnchantments = HashSet(newEnchantments) + } + +} 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/plugins/EnchantmentSquaredDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/EnchantmentSquaredDependency.kt new file mode 100644 index 0000000..d769986 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/EnchantmentSquaredDependency.kt @@ -0,0 +1,200 @@ +package xyz.alexcrea.cuanvil.dependency.plugins + +import io.delilaheve.CustomAnvil +import me.athlaeos.enchantssquared.enchantments.CustomEnchant +import me.athlaeos.enchantssquared.listeners.AnvilListener +import me.athlaeos.enchantssquared.managers.CustomEnchantManager +import org.bukkit.Material +import org.bukkit.NamespacedKey +import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.event.inventory.PrepareAnvilEvent +import org.bukkit.inventory.ItemStack +import org.bukkit.plugin.Plugin +import xyz.alexcrea.cuanvil.api.ConflictBuilder +import xyz.alexcrea.cuanvil.api.EnchantmentApi +import xyz.alexcrea.cuanvil.api.MaterialGroupApi +import xyz.alexcrea.cuanvil.enchant.CAEnchantment +import xyz.alexcrea.cuanvil.enchant.CAEnchantmentRegistry +import xyz.alexcrea.cuanvil.enchant.bulk.EnchantSquaredBulkOperation +import xyz.alexcrea.cuanvil.enchant.wrapped.CAEnchantSquaredEnchantment +import xyz.alexcrea.cuanvil.group.IncludeGroup +import java.util.* + +class EnchantmentSquaredDependency(private val enchantmentSquaredPlugin: Plugin) { + + init { + CustomAnvil.instance.logger.info("Enchantment Squared Detected !") + CustomAnvil.instance.logger.info("Please be aware that Custom Anvil is bypassing Enchantment Squared ") + CustomAnvil.instance.logger.info( + "compatible_with, " + + "disable_anvil, " + + "incompatible_vanilla_enchantments, " + + "incompatible_custom_enchantments and max_level " + + "configuration values.") + } + + fun disableAnvilListener(){ + PrepareAnvilEvent.getHandlerList().unregister(this.enchantmentSquaredPlugin) + + // Find the anvil click event + var toRemove: AnvilListener? = null + for (registered in InventoryClickEvent.getHandlerList().registeredListeners) { + val listener = registered.listener + if(listener is AnvilListener) { + toRemove = listener + break + } + } + + if(toRemove != null) + InventoryClickEvent.getHandlerList().unregister(toRemove) + + } + + fun registerEnchantments(){ + CustomAnvil.instance.logger.info("Preparing Enchantment Squared compatibility...") + + // Register enchantments + for (enchant in CustomEnchantManager.getInstance().allEnchants.values) { + EnchantmentApi.registerEnchantment(CAEnchantSquaredEnchantment(enchant)) + } + + // Register bulk operation + val bulkOpperations = EnchantSquaredBulkOperation() + EnchantmentApi.addBulkGet(bulkOpperations) + EnchantmentApi.addBulkClean(bulkOpperations) + + } + + fun getEnchantmentsSquared(item: ItemStack, enchantments: MutableMap) { + val customEnchants = CustomEnchantManager.getInstance().getItemsEnchantsFromPDC(item) + + customEnchants.forEach{ + (enchantment, level ) -> enchantments[getWrappedEnchant(enchantment)] = level + } + + } + + fun getKeyFromEnchant(enchant: CustomEnchant): NamespacedKey{ + return NamespacedKey.fromString(enchant.type.lowercase(Locale.getDefault()), this.enchantmentSquaredPlugin)!! + } + private fun getWrappedEnchant(enchant: CustomEnchant): CAEnchantment { + return CAEnchantment.getByKey(getKeyFromEnchant(enchant))!! + } + + fun registerPluginConfiguration(){ + CustomAnvil.instance.logger.info("Preparing Enchantment Squared config...") + + // Prepare enchantments + val esEnchantments = ArrayList() + CustomEnchantManager.getInstance().allEnchants.forEach { (_, enchant) -> + esEnchantments.add(getWrappedEnchant(enchant) as CAEnchantSquaredEnchantment) + } + + // Write groups and conflicts + writeMissingGroups() + writeMaterialRestriction(esEnchantments) + writeEnchantmentConflicts(esEnchantments) + + CustomAnvil.instance.logger.info("Enchantment Squared should now work as expected !") + } + + private fun writeMissingGroups(){ + // Write group that do not exist on custom anvil. + val shield = IncludeGroup("shield") + shield.addToPolicy(Material.SHIELD.key) + MaterialGroupApi.addMaterialGroup(shield) + + val elytra = IncludeGroup("elytra") + elytra.addToPolicy(Material.ELYTRA.key) + MaterialGroupApi.addMaterialGroup(elytra) + + val trinkets = IncludeGroup("trinkets") + trinkets.addToPolicy(Material.ROTTEN_FLESH.key) + MaterialGroupApi.addMaterialGroup(trinkets) + } + + private fun writeMaterialRestriction(esEnchantments: List){ + for (enchantment in esEnchantments) { + val conflict = ConflictBuilder("restriction_${enchantment.key.key}", CustomAnvil.instance) + conflict.addEnchantment(enchantment) + + // enchanted book is allowed in any case. + conflict.addExcludedGroup("enchanted_book") + + // Get allowed groups + for (esGroup in enchantment.enchant.compatibleItems) { + val caGroup = esGroupToCAGroup(esGroup) + if(caGroup == null){ + CustomAnvil.instance.logger.info("Could not find equivalent custom anvil group for $esGroup") + continue + } + conflict.addExcludedGroup(caGroup) + } + + conflict.registerIfAbsent() + } + } + + private fun writeEnchantmentConflicts(esEnchantments: List){ + val otherEnchants = ArrayList() + otherEnchants.addAll(CAEnchantmentRegistry.getInstance().values()) + + for (enchantment in esEnchantments) { + otherEnchants.remove(enchantment) + + // find conflicting enchantment. + for (otherEnchant in otherEnchants) { + if(enchantment.enchant.conflictsWithEnchantment(otherEnchant.name)){ + writeConflict(enchantment, otherEnchant) + } + } + } + } + + private fun writeConflict(enchantment1: CAEnchantment, enchantment2: CAEnchantment){ + val conflict = ConflictBuilder("${enchantment1.name}_with_${enchantment2.name}_conflict", CustomAnvil.instance) + + conflict.addEnchantment(enchantment1).addEnchantment(enchantment2) + + conflict.setMaxBeforeConflict(1) + conflict.registerIfAbsent() + } + + /** + * Transform an Enchantment Squared group to a Custom Anvil group + */ + private fun esGroupToCAGroup(esGroup: String): String? { + return when(esGroup){ + "SWORDS" -> "swords" + "BOWS" -> "bow" + "CROSSBOWS" -> "crossbow" + "TRIDENTS" -> "trident" + "HELMETS" -> "helmets" + "CHESTPLATES" -> "chestplate" + "LEGGINGS" -> "leggings" + "BOOTS" -> "boots" + "SHEARS" -> "shears" + "FLINTANDSTEEL" -> "flint_and_steel" + "FISHINGROD" -> "fishing_rod" + "ELYTRA", "ELYTRAS" -> "elytra" + "PICKAXES" -> "pickaxes" // not on this plugin by default + "AXES" -> "axes" + "SHOVELS" -> "shovels" // not on this plugin by default + "HOES" -> "hoes" // not on this plugin by default + "SHIELDS" -> "shield" // not on this plugin by default + "TRINKETS" -> "trinkets" // not the idea way as it will also allow non trinkets rotten flesh to be enchanted. + "ALL" -> "everything" + else -> null + } + } + + fun stripLore(item: ItemStack) { + CustomEnchantManager.getInstance().removeAllEnchants(item) + } + + fun updateLore(item: ItemStack) { + CustomEnchantManager.getInstance().updateLore(item) + } + +} 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/plugins/HavenBagsDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/HavenBagsDependency.kt new file mode 100644 index 0000000..6f30497 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/HavenBagsDependency.kt @@ -0,0 +1,85 @@ +package xyz.alexcrea.cuanvil.dependency.plugins + +import io.delilaheve.CustomAnvil +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.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.anvil.AnvilXpUtil + +class HavenBagsDependency { + + init { + CustomAnvil.instance.logger.info("Heaven Bags Detected !") + } + + private lateinit var bagUpgrade: BagUpgrade + private lateinit var bagSkin: BagSkin + + fun redirectListeners() { + val toUnregister = ArrayList() + // get required PrepareAnvilEvent listener + for (registeredListener in PrepareAnvilEvent.getHandlerList().registeredListeners) { + val listener = registeredListener.listener + + if (listener is BagUpgrade) { + bagUpgrade = listener + toUnregister.add(registeredListener) + } + + if (listener is BagSkin) { + bagSkin = listener + toUnregister.add(registeredListener) + } + } + + for (listener in toUnregister) { + PrepareAnvilEvent.getHandlerList().unregister(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 + bagSkin.onPrepareAnvil(event) + if (event.result != null) { + CustomAnvil.log("Detected pre anvil heaven bag anvil skin.") + 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.setAnvilInvCost(event.inventory, event.view, player, AnvilCost(event.inventory.repairCost)) + return true + } + + event.result = previousResult + return false + } + + fun testAnvilResult(event: InventoryClickEvent, inventory: AnvilInventory): Boolean { + val result = inventory.getItem(PrepareAnvilListener.ANVIL_OUTPUT_SLOT)?.clone() + + if (HavenBags.IsBag(result)) { + CustomAnvil.log("Detected anvil click haven bag bypass.") + bagUpgrade.onInventoryClick(event) + bagSkin.onInventoryClick(event) + return true + } + + return false + } + +} 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/plugins/LegacyEcoEnchantDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/LegacyEcoEnchantDependency.kt new file mode 100644 index 0000000..60f00b8 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/LegacyEcoEnchantDependency.kt @@ -0,0 +1,46 @@ +package xyz.alexcrea.cuanvil.dependency.plugins + +import com.willfp.ecoenchants.enchantments.EcoEnchant +import com.willfp.ecoenchants.enchantments.EcoEnchants +import org.bukkit.enchantments.Enchantment +import xyz.alexcrea.cuanvil.api.EnchantmentApi +import xyz.alexcrea.cuanvil.enchant.wrapped.CALegacyEcoEnchant + +class LegacyEcoEnchantDependency { + + + private var ecoEnchantOldEnchantments: MutableSet? = null + fun registerEnchantments() { + val enchantments = EcoEnchants.values() + for (ecoEnchant in enchantments) { + ecoEnchant as Enchantment + + EnchantmentApi.unregisterEnchantment(ecoEnchant) // As eco enchants is loaded before custom anvil and register enchantment to registry, we need to unregister old "vanilla" enchant. + EnchantmentApi.registerEnchantment(CALegacyEcoEnchant(ecoEnchant, ecoEnchant)) + } + + ecoEnchantOldEnchantments = HashSet(enchantments) + } + + fun handleConfigReload() { + // Should not happen in known case. + if (this.ecoEnchantOldEnchantments == null) return + + val newEnchantments = EcoEnchants.values() + + // Add new enchantments + for (ecoEnchant in newEnchantments) + if (!this.ecoEnchantOldEnchantments!!.contains(ecoEnchant)) + EnchantmentApi.registerEnchantment(CALegacyEcoEnchant(ecoEnchant, ecoEnchant as Enchantment)) + + + // Remove old enchantments that not now currently used + this.ecoEnchantOldEnchantments!!.removeAll(newEnchantments) + for (oldEnchantment in this.ecoEnchantOldEnchantments!!) { + EnchantmentApi.unregisterEnchantment(oldEnchantment as Enchantment) + } + + this.ecoEnchantOldEnchantments = HashSet(newEnchantments) + } + +} 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/protocolib/NoProtocoLib.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/protocolib/NoProtocoLib.kt deleted file mode 100644 index 3591baa..0000000 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/protocolib/NoProtocoLib.kt +++ /dev/null @@ -1,13 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.protocolib - -import org.bukkit.entity.Player - -class NoProtocoLib: PacketManager { - override val isProtocoLibInstalled: Boolean - get() = false - - // ProtocoLib not installed: We do nothing - - override fun setInstantBuild(player: Player, instantBuild: Boolean) {} - -} diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/protocolib/PacketManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/protocolib/PacketManager.kt deleted file mode 100644 index 65830ac..0000000 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/protocolib/PacketManager.kt +++ /dev/null @@ -1,11 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.protocolib - -import org.bukkit.entity.Player - -interface PacketManager { - - val isProtocoLibInstalled: Boolean - - fun setInstantBuild(player: Player, instantBuild: Boolean) - -} diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/scheduler/BukkitScheduler.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/scheduler/BukkitScheduler.kt new file mode 100644 index 0000000..8c04162 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/scheduler/BukkitScheduler.kt @@ -0,0 +1,17 @@ +package xyz.alexcrea.cuanvil.dependency.scheduler + +import org.bukkit.Bukkit +import org.bukkit.entity.Entity +import org.bukkit.plugin.Plugin + +class BukkitScheduler : TaskScheduler { + + 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 { + 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 289f058..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() } @@ -25,30 +26,54 @@ 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) + 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: NamespacedKey): AbstractMaterialGroup { + for (material in materials) { + addToPolicy(material) + } + return this + } /** * Push a group to this group to follow this group policy + * @return this instance. */ - abstract fun addToPolicy(other: AbstractMaterialGroup) + abstract fun addToPolicy(other: AbstractMaterialGroup): AbstractMaterialGroup + + /** + * Push a list of group to this group to follow this group policy + * @return this instance. + */ + fun addAll(vararg otherList: AbstractMaterialGroup): AbstractMaterialGroup { + for (group in otherList) { + addToPolicy(group) + } + return this + } /** * 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) } @@ -78,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 2885237..59841ac 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictGroup.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictGroup.kt @@ -2,33 +2,76 @@ package xyz.alexcrea.cuanvil.group import io.delilaheve.CustomAnvil import org.bukkit.Material -import xyz.alexcrea.cuanvil.enchant.WrappedEnchantment +import org.bukkit.NamespacedKey +import xyz.alexcrea.cuanvil.enchant.CAEnchantment class EnchantConflictGroup( - private val name: String, + val name: String, private val cantConflict: AbstractMaterialGroup, - var minBeforeBlock: Int + var minBeforeBlock: Int, ) { - private val enchantments = HashSet() + private val enchantments = HashSet() + private val conflictsAfterLevel = HashMap() + private val conflictsBeforeLevel = HashMap() - fun addEnchantment(enchant: WrappedEnchantment) { + fun addEnchantment(enchant: CAEnchantment) { enchantments.add(enchant) } + fun addEnchantments(enchants: List) { + 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) { @@ -44,15 +87,45 @@ class EnchantConflictGroup( return this.cantConflict } - fun getEnchants(): HashSet { + fun getEnchants(): HashSet { return enchantments } - fun setEnchants(enchants: Set) { + fun setEnchants(enchants: Set) { enchantments.clear() 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 d41eb38..38d5476 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictManager.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictManager.kt @@ -1,11 +1,16 @@ package xyz.alexcrea.cuanvil.group import io.delilaheve.CustomAnvil -import org.bukkit.Material import org.bukkit.NamespacedKey import org.bukkit.configuration.ConfigurationSection import org.bukkit.enchantments.Enchantment -import xyz.alexcrea.cuanvil.enchant.WrappedEnchantment +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 { @@ -13,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" @@ -24,50 +37,68 @@ class EnchantConflictManager { private const val FUTURE_USE_PATH = "useInFuture" // Default name for a joining group - private const val DEFAULT_GROUP_NAME = "joinedGroup" + const val DEFAULT_GROUP_NAME = "joinedGroup" // 1.20.5 compatibility TODO better update system - private val SWEEPING_EDGE_ENCHANT = - WrappedEnchantment.getByKey(NamespacedKey.minecraft("sweeping_edge")) ?: - WrappedEnchantment.getByKey(Enchantment.SWEEPING_EDGE.key) + private val SWEEPING_EDGE_ENCHANT = Collections.singletonList( + CAEnchantment.getByKey(NamespacedKey.minecraft("sweeping_edge")) + ?: CAEnchantment.getByKey(Enchantment.SWEEPING_EDGE.key) + ) } - private lateinit var conflictMap: HashMap> 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) { - conflictMap = HashMap() conflictList = ArrayList() + // Clear conflict if exist + 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) - addToMap(conflict) - conflictList.add(conflict) + addConflict(conflict) } } - // Add the conflict to the map - private fun addToMap(conflict: EnchantConflictGroup) { + fun addConflict(conflict: EnchantConflictGroup) { + addConflictToEnchantments(conflict) + conflictList.add(conflict) + } + + fun removeConflict(conflict: EnchantConflictGroup) { + removeConflictFromEnchantments(conflict) + conflictList.remove(conflict) + } + + // Add the conflict to enchantments + private fun addConflictToEnchantments(conflict: EnchantConflictGroup) { conflict.getEnchants().forEach { enchant -> - addConflictToConflictMap(enchant, conflict) + enchant.addConflict(conflict) } } - fun addConflictToConflictMap(enchant: WrappedEnchantment, conflict: EnchantConflictGroup) { - if (!conflictMap.containsKey(enchant)) { - conflictMap[enchant] = ArrayList() + // Remove the conflict from enchantments + private fun removeConflictFromEnchantments(conflict: EnchantConflictGroup) { + conflict.getEnchants().forEach { enchant -> + enchant.removeConflict(conflict) } - conflictMap[enchant]!!.add(conflict) - } - - fun removeConflictFromMap(enchant: WrappedEnchantment, conflict: EnchantConflictGroup): Boolean { - return conflictMap[enchant]!!.remove(conflict) } // create and read a conflict from a yaml section @@ -83,34 +114,68 @@ class EnchantConflictManager { // Read and add enchantment to conflict val enchantList = section.getStringList(ENCH_LIST_PATH) for (enchantName in enchantList) { - val enchant = getEnchantByName(enchantName) - if (enchant == null) { - if (!futureUse) { + val enchants = getEnchantByIdentifier(enchantName) + if (enchants.isEmpty()) { + if (!futureUse) { //TODO future use will be deprecated once the new update system is finished CustomAnvil.instance.logger.warning("Enchantment $enchantName do not exist but was asked for conflict $conflictName") } continue } - conflict.addEnchantment(enchant) + conflict.addEnchantments(enchants) } - if (conflict.getEnchants().size == 0) { - if (!futureUse) { + if (conflict.getEnchants().isEmpty()) { + if (!futureUse) { //TODO future use will be deprecated once the new update system is finished CustomAnvil.instance.logger.warning("Conflict $conflictName do not have valid enchantment, it will not do anything") } } + 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 getEnchantByName(enchantName: String): WrappedEnchantment? { + 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) { + val enchantment = CAEnchantment.getByKey(key) + if (enchantment != null) return Collections.singletonList(enchantment) + + } // Temporary solution for 1.20.5 - when(enchantName){ - "sweeping", "sweeping_edge" -> { + when (enchantName) { + "minecraft:sweeping", "sweeping", + "minecraft:sweeping_edge", "sweeping_edge" -> { return SWEEPING_EDGE_ENCHANT } } - return WrappedEnchantment.getByName(enchantName) + return CAEnchantment.getListByName(enchantName) } @@ -144,42 +209,104 @@ class EnchantConflictManager { ): AbstractMaterialGroup { val group = itemManager.get(groupName) if (group == null) { - CustomAnvil.instance.logger.warning("Group $groupName do not exist but is ask by conflict $conflictName") + CustomAnvil.instance.logger.warning("Material group $groupName do not exist but is ask by conflict $conflictName") return IncludeGroup("error_placeholder") } return group } - fun isConflicting(base: Set, mat: Material, newEnchant: WrappedEnchantment): ConflictType { - CustomAnvil.verboseLog("Testing conflict for ${newEnchant.key} on ${mat.key}") - val conflictList = conflictMap[newEnchant] ?: return ConflictType.NO_CONFLICT - CustomAnvil.verboseLog("Did not get skipped") + 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(base, 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.SMALL_CONFLICT - CustomAnvil.verboseLog("Small conflict, continuing") + result = ConflictType.ITEM_CONFLICT + CustomAnvil.verboseLog("Small conflict (${conflict.name}), continuing") } else { - CustomAnvil.verboseLog("Big conflict, probably stoping") - return ConflictType.BIG_CONFLICT + CustomAnvil.verboseLog("Big conflict (${conflict.name}), stopping") + return ConflictType.ENCHANTMENT_CONFLICT } } } + + val immutableEnchants = Collections.unmodifiableMap(appliedEnchants) + for (appliedEnchant in appliedEnchants.keys) { + 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)) { + val partialItem = createPartialResult(item, immutableEnchants) + + if (newEnchant.isItemConflict(immutableEnchants, type, partialItem)) { + return ConflictType.ITEM_CONFLICT + } + + } + return result } + private fun createPartialResult(item: ItemStack, enchantments: Map): ItemStack { + val newItem = item.clone() + + CAEnchantment.clearEnchants(newItem) + enchantments.forEach { enchantment -> + enchantment.key.addEnchantmentUnsafe(newItem, enchantment.value) + } + + return newItem + } + } -enum class ConflictType { - NO_CONFLICT, - SMALL_CONFLICT, - BIG_CONFLICT +/** + * Provide information about the current conflict. + */ +enum class ConflictType(private val importance: Int) { + /** + * Allowed to proceed the anvil process. + */ + NO_CONFLICT(0), + /** + * Inform that the anvil process should not change the current applied enchantment. + */ + ITEM_CONFLICT(1), + + /** + * Inform that the anvil process should not change the current applied enchantment. + * Also add sacrificeIllegalCost for every enchantment marked as big conflict. + */ + ENCHANTMENT_CONFLICT(2); + + fun getWorstConflict(otherConflict: ConflictType): ConflictType { + return if (this.importance > otherConflict.importance) this + else otherConflict + + } } \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/group/ExcludeGroup.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/group/ExcludeGroup.kt index 247cb83..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,14 +21,18 @@ class ExcludeGroup(name: String) : AbstractMaterialGroup(name) { return false } - override fun addToPolicy(mat: Material) { - includedMaterial.remove(mat) - groupItems.remove(mat) + override fun addToPolicy(type: NamespacedKey): ExcludeGroup { + includedMaterial.remove(type) + groupItems.remove(type) + + return this } - override fun addToPolicy(other: AbstractMaterialGroup) { + override fun addToPolicy(other: AbstractMaterialGroup): ExcludeGroup { includedGroup.add(other) groupItems.removeAll(other.getMaterials()) + + return this } override fun setGroups(groups: MutableSet) { @@ -56,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 968ceb1..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,14 +21,18 @@ class IncludeGroup(name: String) : AbstractMaterialGroup(name) { return false } - override fun addToPolicy(mat: Material) { - includedMaterial.add(mat) - groupItems.add(mat) + override fun addToPolicy(type: NamespacedKey): IncludeGroup { + includedMaterial.add(type) + groupItems.add(type) + + return this } - override fun addToPolicy(other: AbstractMaterialGroup) { + override fun addToPolicy(other: AbstractMaterialGroup): IncludeGroup { includedGroup.add(other) groupItems.addAll(other.getMaterials()) + + return this } override fun setGroups(groups: MutableSet) { @@ -43,7 +48,7 @@ class IncludeGroup(name: String) : AbstractMaterialGroup(name) { } } - override fun setNonGroupInheritedMaterials(materials: EnumSet) { + override fun setNonGroupInheritedMaterials(materials: Set) { super.setNonGroupInheritedMaterials(materials) updateMaterials() @@ -62,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 new file mode 100644 index 0000000..d707b56 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilCloseListener.kt @@ -0,0 +1,25 @@ +package xyz.alexcrea.cuanvil.listener + +import org.bukkit.GameMode +import org.bukkit.entity.Player +import org.bukkit.event.EventHandler +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 { + + @EventHandler + fun onAnvilClose(event: InventoryCloseEvent){ + val player = event.player + if(event.inventory !is AnvilInventory) return + if(player is Player && GameMode.CREATIVE != player.gameMode){ + 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 new file mode 100644 index 0000000..e393dbd --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt @@ -0,0 +1,613 @@ +package xyz.alexcrea.cuanvil.listener + +import io.delilaheve.CustomAnvil +import io.delilaheve.util.ConfigOptions +import io.delilaheve.util.ItemUtil.canMergeWith +import org.bukkit.GameMode +import org.bukkit.Material +import org.bukkit.entity.Player +import org.bukkit.event.Event +import org.bukkit.event.EventHandler +import org.bukkit.event.Listener +import org.bukkit.event.inventory.ClickType +import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.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.util.CustomRecipeUtil +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.AtomicReference +import kotlin.math.min + +class AnvilResultListener : Listener { + + companion object { + // static slot container + private val NO_SLOT = SlotContainer(SlotType.NO_SLOT, 0) + private val CURSOR_SLOT = SlotContainer(SlotType.CURSOR, 0) + } + + /** + * Event handler logic for when a player is trying to pull an item out of the anvil + */ + @EventHandler(ignoreCancelled = true) + fun anvilExtractionCheck(event: InventoryClickEvent) { + val player = event.whoClicked as? Player ?: return + val inventory = event.inventory as? AnvilInventory ?: return + val view = event.view + + if (event.rawSlot != ANVIL_OUTPUT_SLOT) { + return + } + + // 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) { + return + } + + // Test custom recipe + val customRecipeResult = AnvilMergeLogic.testCustomRecipe(view, inventory, player, leftItem, rightItem) + if (!customRecipeResult.isEmpty()) { + onCustomCraft( + event, player, inventory, + leftItem, rightItem, customRecipeResult + ) + return + } + + // Do not continue if there was no change + if ((output == inventory.getItem(ANVIL_INPUT_LEFT))) { + return + } + + // Rename + if (rightItem == null) { + 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) { + val result = AnvilMergeLogic.doMerge(view, inventory, player, leftItem, rightItem) + + extractAnvilResult( + event, player, inventory, + null, 0, + null, 0, + result + ) + return + } + + // Unit repair + val unitRepairResult = AnvilMergeLogic.testUnitRepair( + view, inventory, player, + leftItem, rightItem + ) + if (!unitRepairResult.isEmpty()) { + onUnitRepairExtract( + rightItem, event, player, inventory, + unitRepairResult + ) + return + } + + // For lore edit + 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 + } + } + + private fun onCustomCraft( + event: InventoryClickEvent, + player: Player, + inventory: AnvilInventory, + leftItem: ItemStack, + rightItem: ItemStack?, + result: CustomCraftResult, + ) { + val recipe = result.recipe!! + val rawCost = result.customCraftCost.rawCost + val finalCost = + if (recipe.removeExactLinearXp) rawCost + else AnvilXpUtil.calculateLevelForXp(rawCost) + + CustomAnvil.log( + "gamemode: ${player.gameMode != GameMode.CREATIVE}, " + + "cost: $finalCost, level: ${player.level}, " + + "result: ${player.totalExperience < finalCost} ${player.level < finalCost}" + ) + + 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 + val slotDestination = getActionSlot(event, player) + if (slotDestination.type == SlotType.NO_SLOT) return + + // Handle not creative middle click... + if (event.click != ClickType.MIDDLE && + !handleCustomCraftClick( + event, + inventory, + player, + leftItem, + rightItem, + result + ) + ) return + + // Finally, we add the item to the player + if (slotDestination.type == SlotType.CURSOR) { + player.setItemOnCursor(result.item) + } else {// We assume SlotType == SlotType.INVENTORY + player.inventory.setItem(slotDestination.slot, result.item) + } + } + + private fun handleCustomCraftClick( + event: InventoryClickEvent, + inventory: AnvilInventory, player: Player, + leftItem: ItemStack, rightItem: ItemStack?, + 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 + + rightItem.amount -= amount * recipe.rightItem!!.amount + inventory.setItem(ANVIL_INPUT_RIGHT, rightItem) + } + + leftItem.amount -= amount * recipe.leftItem!!.amount + inventory.setItem(ANVIL_INPUT_LEFT, leftItem) + + removeCustomCraftCost(player, result) + + // Then we try to find the new values for the anvil + val newAmount = CustomRecipeUtil.getCustomRecipeAmount(recipe, leftItem, rightItem) + + CustomAnvil.verboseLog("new amount is $newAmount") + if (newAmount <= 0 || recipe.exactCount) { + inventory.setItem(ANVIL_OUTPUT_SLOT, null) + } else { + val resultItem: ItemStack = recipe.resultItem!!.clone() + resultItem.amount *= newAmount + + val newXp = newAmount * newAmount + + inventory.repairCost = newXp + event.view.setProperty(InventoryView.Property.REPAIR_COST, newXp) + + inventory.setItem(ANVIL_OUTPUT_SLOT, resultItem) + + player.updateInventory() + } + 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, + inventory: AnvilInventory, + leftItem: ItemStack?, + leftRemoveCount: Int, + rightItem: ItemStack?, + rightRemoveCount: 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 + + processCost(inventory, player, cost) + if (!cost.valid && player.gameMode != GameMode.CREATIVE) return false + + // Where should we get the item + val slotDestination = getActionSlot(event, player) + if (slotDestination.type == SlotType.NO_SLOT) return false + + // 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) + + if (rightItem != null) rightItem.amount -= rightRemoveCount + inventory.setItem(ANVIL_INPUT_RIGHT, rightItem) + + inventory.setItem(ANVIL_OUTPUT_SLOT, null) + + } + + // Finally, we add the item to the player + if (SlotType.CURSOR == slotDestination.type) { + player.setItemOnCursor(result.item) + } else {// We assume SlotType == SlotType.INVENTORY + player.inventory.setItem(slotDestination.slot, result.item) + } + + // TODO probably anvil damage & sound here ?? + return true + } + + private fun processCost(inventory: AnvilInventory, player: Player, cost: AnvilCost) { + var sum = cost.repair + + if ( + !ConfigOptions.doRemoveCostLimit && + ConfigOptions.doCapCost + ) { + val final = min(sum, ConfigOptions.maxAnvilCost) + cost.generic += (final - sum) + + sum = final + } + + 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 onUnitRepairExtract( + rightItem: ItemStack, + event: InventoryClickEvent, + player: Player, + inventory: AnvilInventory, + result: UnitRepairResult, + ) { + // We give the item manually + extractAnvilResult( + event, player, inventory, + null, 0, + rightItem, result.repairAmount, + result + ) + } + + private fun handleBookLoreEdit( + event: InventoryClickEvent, + inventory: AnvilInventory, + player: Player, + leftItem: ItemStack, + rightItem: ItemStack, + result: LoreEditResult + ) { + if (result.type.isAppend) + handleBookLoreAppend(event, inventory, player, rightItem, result) + else + handleBookLoreRemove(event, inventory, player, leftItem, rightItem, result) + } + + private fun handleBookLoreAppend( + event: InventoryClickEvent, + inventory: AnvilInventory, + player: Player, + rightItem: ItemStack, + result: LoreEditResult + ) { + val bookMeta = rightItem.itemMeta as BookMeta? ?: return + + // 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 + } + + extractAnvilResult( + event, player, inventory, + null, 0, + clearedBook, 0, + result + ) + } + + private fun handleBookLoreRemove( + event: InventoryClickEvent, + inventory: AnvilInventory, + player: Player, + leftItem: ItemStack, + rightItem: ItemStack, + result: LoreEditResult + ) { + val bookMeta = rightItem.itemMeta as BookMeta? ?: return + + // fill book meta + val lore = DependencyManager.stripLore(leftItem) + if (lore.isEmpty()) return + + val rightCopy: ItemStack? + if (LoreEditType.REMOVE_BOOK.doConsume) { + rightCopy = null + } else { + // Uncolor the page + AnvilLoreEditUtil.uncolorLines(player, lore, LoreEditType.REMOVE_BOOK) + + val bookPage = StringBuilder() + lore.forEach { + if (bookPage.isNotEmpty()) bookPage.append('\n') + if (it == null) return@forEach + + bookPage.append(MiniMessageUtil.plain_text_mm.serialize(it)) + } + + 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( + event: InventoryClickEvent, + inventory: AnvilInventory, + player: Player, + leftItem: ItemStack, + rightItem: ItemStack, + result: LoreEditResult + ) { + if (result.type.isAppend) + handlePaperLoreAppend(event, inventory, player, rightItem, result) + else + handlePaperLoreRemove(event, inventory, player, leftItem, rightItem, result) + } + + private fun handlePaperLoreAppend( + event: InventoryClickEvent, + inventory: AnvilInventory, + player: Player, + rightItem: ItemStack, + result: LoreEditResult + ) { + val paperMeta = rightItem.itemMeta ?: return + + + val paperCopy: ItemStack? + if (LoreEditType.APPEND_PAPER.doConsume) { + paperCopy = null + } else { + // Remove custom name to paper + paperCopy = rightItem.clone() + paperCopy.amount = 1 + paperMeta.setComponentDisplayName(null) + + // Remove pcd name + AnvilMergeLogic.processPCD(paperMeta, player, null) + + 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 + ) + } + } + + /** + * Get the destination slot or "NO_SLOT" slot container if there is no slot available + */ + private fun getActionSlot(event: InventoryClickEvent, player: Player): SlotContainer { + if (event.isShiftClick) { + val inventory = player.inventory + val firstEmpty = inventory.firstEmpty() + if (firstEmpty == -1) { + return NO_SLOT + } + //check hotbare full + var slotIndex = 8 + while (slotIndex >= 0 && ((inventory.getItem(slotIndex)?.type ?: Material.AIR) != Material.AIR)) { + slotIndex-- + } + if (slotIndex >= 0) { + return SlotContainer(SlotType.INVENTORY, slotIndex) + } + slotIndex = 35 //4*9 - 1 (max of player inventory) + while (slotIndex >= 9 && ((inventory.getItem(slotIndex)?.type ?: Material.AIR) != Material.AIR)) { + slotIndex-- + } + if (slotIndex < 9) { + return NO_SLOT + } + return SlotContainer(SlotType.INVENTORY, slotIndex) + } else if (player.itemOnCursor.type != Material.AIR) return NO_SLOT + return CURSOR_SLOT + } + + private class SlotContainer(val type: SlotType, val slot: Int) + private enum class SlotType { + CURSOR, + INVENTORY, + NO_SLOT + + } + +} diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/ChatEventListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/ChatEventListener.kt index 4ea85fd..f166fa3 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/ChatEventListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/ChatEventListener.kt @@ -1,12 +1,12 @@ package xyz.alexcrea.cuanvil.listener import io.delilaheve.CustomAnvil -import org.bukkit.Bukkit import org.bukkit.entity.HumanEntity import org.bukkit.event.EventHandler import org.bukkit.event.Listener import org.bukkit.event.player.AsyncPlayerChatEvent import org.bukkit.event.player.PlayerQuitEvent +import xyz.alexcrea.cuanvil.dependency.DependencyManager import java.util.* import java.util.concurrent.ConcurrentHashMap import java.util.function.Consumer @@ -39,9 +39,11 @@ class ChatEventListener : Listener { event.isCancelled = true // sync callback with default server thread - Bukkit.getScheduler().runTask(CustomAnvil.instance, Runnable { + DependencyManager.scheduler.scheduleOnEntity( + CustomAnvil.instance, player, + Runnable { eventCallback.accept(event.message) - }) + }, 0L) } } \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt new file mode 100644 index 0000000..0217983 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt @@ -0,0 +1,181 @@ +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.ItemUtil.canMergeWith +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.MaterialUtil.isAir +import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil +import xyz.alexcrea.cuanvil.util.dialog.AnvilRenameDialogUtil + +/** + * Listener for anvil events + */ +class PrepareAnvilListener : Listener { + + companion object { + + // Anvil's output slot + const val ANVIL_INPUT_LEFT = 0 + const val ANVIL_INPUT_RIGHT = 1 + const val ANVIL_OUTPUT_SLOT = 2 + + var IS_EMPTY_TEST = false + } + + /** + * Event handler logic for when an anvil contains items to be combined + */ + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + fun anvilCombineCheck(event: PrepareAnvilEvent) { + 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)) { + // 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 + var result: AnvilResult = testCustomRecipe(view, inventory, player, first, second) + if (!result.isEmpty()) + return result + + // Test rename lonely item + 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!!)) + return doMerge(view, inventory, player, first, second) + + // Test for unit repair + result = testUnitRepair(view, inventory, player, first, second) + if (!result.isEmpty()) + return result + + // Test for lore edit + result = testLoreEdit(player, first, second) + if (!result.isEmpty()) + return result + + return AnvilResult.EMPTY + } + + private fun tryRenameDialog( + player: HumanEntity, + event: PrepareAnvilEvent + ) { + if(!ConfigOptions.canUseDialogRename(player)) return + + AnvilRenameDialogUtil.anvilRenameDialog.tryShowDialog(player, event) + } + + private fun isImmutable(item: ItemStack?): Boolean { + if (item.isAir) return false + + 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 + } + AnvilXpUtil.setAnvilInvCost(event.inventory, event.view, player, result.cost, result.ignoreXpRules) + } + +} \ 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 c7e3038..fd079c3 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/recipe/AnvilCustomRecipe.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/recipe/AnvilCustomRecipe.kt @@ -3,16 +3,22 @@ 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( - private val name: String, + val name: String, var exactCount: Boolean, //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,41 +81,59 @@ 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(){ + fun saveToFile(writeFile: Boolean, doBackup: Boolean) { val fileConfig = ConfigHolder.CUSTOM_RECIPE_HOLDER.config - fileConfig.set("$name.$EXACT_COUNT_CONFIG", exactCount) + fileConfig["$name.$EXACT_COUNT_CONFIG"] = exactCount //fileConfig.set("$name.$EXACT_LEFT_CONFIG", exactLeft) //fileConfig.set("$name.$EXACT_RIGHT_CONFIG", exactRight) - fileConfig.set("$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.set("$name.$LEFT_ITEM_CONFIG", leftItem) - fileConfig.set("$name.$RIGHT_ITEM_CONFIG", rightItem) - fileConfig.set("$name.$RESULT_ITEM_CONFIG", resultItem) + fileConfig["$name.$LEFT_ITEM_CONFIG"] = leftItem + fileConfig["$name.$RIGHT_ITEM_CONFIG"] = rightItem + fileConfig["$name.$RESULT_ITEM_CONFIG"] = resultItem - if (GuiSharedConstant.TEMPORARY_DO_SAVE_TO_DISK_EVERY_CHANGE) { - ConfigHolder.CUSTOM_RECIPE_HOLDER.saveToDisk(GuiSharedConstant.TEMPORARY_DO_BACKUP_EVERY_SAVE) + if (writeFile) { + ConfigHolder.CUSTOM_RECIPE_HOLDER.saveToDisk(doBackup) } } - fun updateFromFile(){ + @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 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 @@ -129,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 !") @@ -165,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/recipe/CustomAnvilRecipeManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/recipe/CustomAnvilRecipeManager.kt index 8f9dece..f271d90 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/recipe/CustomAnvilRecipeManager.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/recipe/CustomAnvilRecipeManager.kt @@ -9,17 +9,17 @@ class CustomAnvilRecipeManager { lateinit var recipeList: ArrayList - lateinit var recipeByMat: LinkedHashMap> + lateinit var recipeByMat: HashMap> fun prepareRecipes(config: FileConfiguration) { recipeList = ArrayList() - recipeByMat = LinkedHashMap() + recipeByMat = HashMap() // read all configs val keys = config.getKeys(false) for (key in keys) { val recipe = AnvilCustomRecipe.getFromConfig(key) - if(recipe == null){ + if (recipe == null) { CustomAnvil.log("Can't load recipe $key") continue } @@ -30,34 +30,34 @@ class CustomAnvilRecipeManager { } - fun cleanAddNew(recipe: AnvilCustomRecipe){ + fun cleanAddNew(recipe: AnvilCustomRecipe) { recipeList.add(recipe) val leftItem = recipe.leftItem - if(leftItem != null){ + if (leftItem != null) { addToMatMap(recipe, leftItem) } } - fun cleanSetLeftItem(recipe: AnvilCustomRecipe, leftItem: ItemStack?){ + fun cleanSetLeftItem(recipe: AnvilCustomRecipe, leftItem: ItemStack?) { // Remove left item mat if exist val oldLeftItem = recipe.leftItem - if(oldLeftItem != null){ + if (oldLeftItem != null) { val oldMat = oldLeftItem.type val test = recipeByMat[oldMat] test!!.remove(recipe) } - if(leftItem != null){ + if (leftItem != null) { addToMatMap(recipe, leftItem) } recipe.leftItem = leftItem } - private fun addToMatMap(recipe: AnvilCustomRecipe, leftItem: ItemStack){ + private fun addToMatMap(recipe: AnvilCustomRecipe, leftItem: ItemStack) { var recipeList = recipeByMat[leftItem.type] - if(recipeList == null){ + if (recipeList == null) { recipeList = ArrayList() recipeByMat[leftItem.type] = recipeList } @@ -65,11 +65,14 @@ class CustomAnvilRecipeManager { } - fun cleanRemove(recipe: AnvilCustomRecipe) { + fun cleanRemove(recipe: AnvilCustomRecipe): Boolean { - recipeList.remove(recipe) - cleanSetLeftItem(recipe, null) + val exist = recipeList.remove(recipe) + if (exist) { + cleanSetLeftItem(recipe, null) + } + return exist; } } \ 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 new file mode 100644 index 0000000..058b25a --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/CustomRecipeUtil.kt @@ -0,0 +1,54 @@ +package xyz.alexcrea.cuanvil.util + +import io.delilaheve.CustomAnvil +import org.bukkit.inventory.ItemStack +import xyz.alexcrea.cuanvil.config.ConfigHolder +import xyz.alexcrea.cuanvil.recipe.AnvilCustomRecipe +import kotlin.math.min + +object CustomRecipeUtil { + + fun getCustomRecipe ( + leftItem: ItemStack, + rightItem: ItemStack?) : AnvilCustomRecipe? { + + val recipeList = ConfigHolder.CUSTOM_RECIPE_HOLDER.recipeManager.recipeByMat[leftItem.type] ?: return null + + CustomAnvil.verboseLog("Testing " + recipeList.size + " recipe...") + for (recipe in recipeList) { + if(recipe.testItem(leftItem, rightItem)){ + return recipe + } + } + + return null + } + + fun getCustomRecipeAmount( + recipe: AnvilCustomRecipe, + leftItem: ItemStack, + rightItem: ItemStack? + ): Int{ + return if(recipe.exactCount) { + if(leftItem.amount != recipe.leftItem!!.amount){ + 0 + }else if(rightItem != null && rightItem.amount != recipe.rightItem!!.amount){ + 0 + }else{ + 1 + } + } + else { + // test amount + val resultItem = recipe.resultItem!! // we know exist as the recipe was returned to us + 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 } + + CustomAnvil.verboseLog("resultItem: $resultItem, maxResultAmount: $maxResultAmount, maxLeftAmount: $maxLeftAmount, maxRightAmount: $maxRightAmount") + + min(min(maxResultAmount, maxLeftAmount), maxRightAmount) + } + } + +} \ No newline at end of file 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 141d3a0..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,10 +23,10 @@ object UnitRepairUtil { if (other == null) return null val config = ConfigHolder.UNIT_REPAIR_HOLDER.config // Get configuration section if exist - val otherName = other.type.name.uppercase() + val otherName = other.customType.key.lowercase() var section = config.getConfigurationSection(otherName) if (section == null) { - section = config.getConfigurationSection(otherName.lowercase()) + section = config.getConfigurationSection(otherName.uppercase()) if (section == null) return null } @@ -44,11 +45,11 @@ 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.uppercase() + val itemName = item.customType.key.lowercase() val repairValue = if (section.isDouble(itemName)) { section.getDouble(itemName) - } else if (section.isDouble(itemName.lowercase())) { - section.getDouble(itemName.lowercase()) + } else if (section.isDouble(itemName.uppercase())) { + section.getDouble(itemName.uppercase()) } else { return null } 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/anvil/AnvilUseTypeUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilUseTypeUtil.kt new file mode 100644 index 0000000..a9c7f39 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilUseTypeUtil.kt @@ -0,0 +1,26 @@ +package xyz.alexcrea.cuanvil.util.anvil + +import io.delilaheve.util.ConfigOptions + +object AnvilUseTypeUtil { + + /* + By stupidity of kotlin not allowing static function outside of companion object I need to do another util class only for 1 function: + Companion object on enum class seems to get initialized AFTER the enum itself + that mean. if you want to call a static function on the enum class itself YOU CAN'T. you need to do this stupid thing + or you can make a stupid top level function OR object that even worse because of global scope pollution + (btw was gpt ""solution"". fortunately I do not "vibe code" so I do what I know instead of stupid AI solution & code) + I mean, this is still global scope pollution bc of a USELESS class that SHOULD not exist but as a class is better than a random *ss function + + sorry for the rent but this made me frustrated + + Note: I still like a lot of part of kotlin compared to java but this part is one that I hate + */ + /** + * Get config path for normal anvil use + */ + fun defaultPath(typeName: String): String { + return "${ConfigOptions.WORK_PENALTY_ROOT}.$typeName" + } + +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilXpUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilXpUtil.kt new file mode 100644 index 0000000..4846f31 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilXpUtil.kt @@ -0,0 +1,308 @@ +package xyz.alexcrea.cuanvil.util.anvil + +import io.delilaheve.CustomAnvil +import io.delilaheve.util.ConfigOptions +import io.delilaheve.util.EnchantmentUtil.enchantmentName +import io.delilaheve.util.ItemUtil.findEnchantments +import io.delilaheve.util.ItemUtil.isEnchantedBook +import org.bukkit.GameMode +import org.bukkit.NamespacedKey +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.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 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 + */ + private fun setAnvilInvXp( + inventory: AnvilInventory, + view: InventoryView, + player: HumanEntity, + anvilCost: Int, + ignoreRules: Boolean = false + ) { + val maximumRepairCost = maximumXpCost(ignoreRules) + + // Try first just in case another plugin, or the test need this + inventory.maximumRepairCost = maximumRepairCost + 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 + * we need to wait for the event to end before overriding it, this ensures that + * we have the final say in the process. */ + DependencyManager.scheduler.scheduleOnEntity( + CustomAnvil.instance, player + ) { + // retry after a tick + inventory.maximumRepairCost = maximumRepairCost + 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) && + (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() + } + } + + /** + * Function to calculate work penalty of anvil work + * Also change result work penalty if right item is not null + */ + fun calculatePenalty(left: ItemStack, right: ItemStack?, result: ItemStack, useType: AnvilUseType): Int { + // Extracted From https://minecraft.wiki/w/Anvil_mechanics#Enchantment_equation + // Calculate work penalty + val penaltyType = ConfigOptions.workPenaltyPart(useType) + val leftPenalty = (left.itemMeta as? Repairable)?.repairCost ?: 0 + val leftExclusivePenalty = findExclusivePenalty(left, useType) + + val rightPenalty = + if (right == null) 0 + else (right.itemMeta as? Repairable)?.repairCost ?: 0 + val rightExclusivePenalty = findExclusivePenalty(right, useType) + + // Increase penalty on fusing or unit repair + if (penaltyType.penaltyIncrease) { + result.itemMeta?.let { + (it as? Repairable)?.repairCost = leftPenalty.coerceAtLeast(rightPenalty) * 2 + 1 + result.itemMeta = it + } + } + if (penaltyType.exclusivePenaltyIncrease) { + val resultPenalty = leftExclusivePenalty.coerceAtLeast(rightExclusivePenalty) * 2 + 1 + setExclusivePenalty(result, resultPenalty, useType) + } + + CustomAnvil.log( + "Calculated penalty: " + + "leftPenalty: $leftPenalty, " + + "rightPenalty: $rightPenalty, " + + "result penalty: ${(result.itemMeta as? Repairable)?.repairCost ?: "none"}" + ) + + var resultSum = 0 + if (penaltyType.penaltyAdditive) { + resultSum += leftPenalty + rightPenalty + } + if (penaltyType.exclusivePenaltyAdditive) { + resultSum += leftExclusivePenalty + rightExclusivePenalty + } + + 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}") + } + + private fun setExclusivePenalty( + result: ItemStack, + resultPenalty: Int, + useType: AnvilUseType + ) { + val key = exclusivePenaltyKey(useType) + + val meta = result.itemMeta!! + meta.persistentDataContainer.set(key, PersistentDataType.INTEGER, resultPenalty) + result.itemMeta = meta + } + + private fun findExclusivePenalty( + item: ItemStack?, + useType: AnvilUseType + ): Int { + if (item == null || !item.hasItemMeta()) return 0 + val key = exclusivePenaltyKey(useType) + + val meta = item.itemMeta!! + return meta.persistentDataContainer.get(key, PersistentDataType.INTEGER) ?: return 0 + } + + /** + * Function to calculate right enchantment values + * it include enchantment placed on final item and conflicting enchantment + */ + fun getRightValues(right: ItemStack, result: ItemStack, cost: AnvilCost) { + // Calculate right value and illegal enchant penalty + + val rightIsFormBook = right.isEnchantedBook() + val resultEnchs = result.findEnchantments() + val resultEnchsKeys = HashMap(resultEnchs) + + for (enchantment in right.findEnchantments()) { + // count enchant as illegal enchant if it conflicts with another enchant or not in result + if ((enchantment.key !in resultEnchsKeys)) { + resultEnchsKeys[enchantment.key] = enchantment.value + val conflictType = ConfigHolder.CONFLICT_HOLDER.conflictManager.isConflicting( + resultEnchsKeys, + result, + enchantment.key + ) + resultEnchsKeys.remove(enchantment.key) + + if (ConflictType.ENCHANTMENT_CONFLICT == conflictType) { + cost.illegalPenalty += ConfigOptions.sacrificeIllegalCost + CustomAnvil.verboseLog("Big conflict. Adding illegal price penalty") + } + continue + } + // We know "enchantment.key in resultEnchs" true + val resultLevel = resultEnchs[enchantment.key]!! + + 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)") + cost.enchantment += value + + } + CustomAnvil.log( + "Calculated right values: " + + "rightValue: ${cost.enchantment}, " + + "illegalPenalty: ${cost.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 new file mode 100644 index 0000000..9d0eb6a --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/config/LoreEditConfigUtil.kt @@ -0,0 +1,103 @@ +package xyz.alexcrea.cuanvil.util.config + +import xyz.alexcrea.cuanvil.config.ConfigHolder.DEFAULT_CONFIG as CONFIG + +object LoreEditConfigUtil { + + // Per edit type configs Path + const val IS_ENABLED = "enabled" + const val FIXED_COST = "fixed_cost" + const val PER_LINE_COST = "per_line_cost" + const val DO_CONSUME = "do_consume" + + // Permission configs path + const val BOOK_PERMISSION_NEEDED = "lore_edit.book_and_quil.use_permission" + const val PAPER_PERMISSION_NEEDED = "lore_edit.paper.use_permission" + + // 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_COST = "remove_color_cost" + + // Lore order config path + const val PAPER_EDIT_ORDER = "lore_edit.paper.order" + + // -------------- + // Default Values + // -------------- + + // Per edit type configs defaults + const val DEFAULT_IS_ENABLED = false + const val DEFAULT_FIXED_COST = 1 + const val DEFAULT_PER_LINE_COST = 0 + const val DEFAULT_DO_CONSUME = false + + // Permission configs defaults + const val DEFAULT_BOOK_PERMISSION_NEEDED = true + const val DEFAULT_PAPER_PERMISSION_NEEDED = true + + // 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_COST = 0 + + // Lore order config default + const val DEFAULT_PAPER_EDIT_ORDER = "end" + + // ------------- + // Config Ranges + // ------------- + + val FIXED_COST_RANGE = 0..1000 + val PER_LINE_COST_RANGE = 0..1000 + + val USE_COLOR_COST_RANGE = 0..1000 + val REMOVE_COLOR_COST_RANGE = 0..1000 + + // ------------------- + // Generic Get methods + // ------------------- + + /** + * Get if we should append/remove at the end or at the start of the lore list + * This may change to an "OrderType" enum or equivalent later + */ + val paperLoreOrderIsEnd: Boolean + get() { + return CONFIG + .config + .getString(PAPER_EDIT_ORDER, DEFAULT_PAPER_EDIT_ORDER) + .equals(DEFAULT_PAPER_EDIT_ORDER, ignoreCase = true) + } + + /** + * If lore edit via book need permission + */ + val bookLoreEditNeedPermission: Boolean + get() { + return CONFIG + .config + .getBoolean(BOOK_PERMISSION_NEEDED, DEFAULT_BOOK_PERMISSION_NEEDED) + } + + /** + * If lore edit via paper need permission + */ + val paperLoreEditNeedPermission: Boolean + get() { + return CONFIG + .config + .getBoolean(PAPER_PERMISSION_NEEDED, DEFAULT_PAPER_PERMISSION_NEEDED) + } + + // ----------------- + // Color Get methods + // ----------------- + +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/config/LoreEditType.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/config/LoreEditType.kt new file mode 100644 index 0000000..c094c71 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/config/LoreEditType.kt @@ -0,0 +1,142 @@ +package xyz.alexcrea.cuanvil.util.config + +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_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.USE_COLOR_COST +import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.USE_COLOR_COST_RANGE +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, 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, + isPaper, isAppend, isMultiLine) + + /** + * If this edit type is enabled + */ + val enabled: Boolean + get() { + return CONFIG + .config + .getBoolean("${rootPath}.${LoreEditConfigUtil.IS_ENABLED}", LoreEditConfigUtil.DEFAULT_IS_ENABLED) + } + + /** + * Fixed cost added to this edit + */ + val fixedCost: Int + get() { + return CONFIG + .config + .getInt("${rootPath}.${LoreEditConfigUtil.FIXED_COST}", LoreEditConfigUtil.DEFAULT_FIXED_COST) + .takeIf { it in LoreEditConfigUtil.FIXED_COST_RANGE } + ?: LoreEditConfigUtil.DEFAULT_FIXED_COST + } + + /** + * Cost added per line added + */ + val perLineCost: Int + get() { + if (!isMultiLine) throw IllegalStateException("Per line cost get on single line edit type") + return CONFIG + .config + .getInt("${rootPath}.${LoreEditConfigUtil.PER_LINE_COST}", LoreEditConfigUtil.DEFAULT_PER_LINE_COST) + .takeIf { it in LoreEditConfigUtil.PER_LINE_COST_RANGE } + ?: LoreEditConfigUtil.DEFAULT_PER_LINE_COST + } + + /** + * If the edit should consume the provided material + */ + val doConsume: Boolean + get() { + return CONFIG + .config + .getBoolean("${rootPath}.${LoreEditConfigUtil.DO_CONSUME}", LoreEditConfigUtil.DEFAULT_DO_CONSUME) + } + + /** + * Allow usage or removal of color code + */ + val allowColorCode: Boolean + get() { + return CONFIG + .config + .getBoolean("$rootPath.$ALLOW_COLOR_CODE", DEFAULT_ALLOW_COLOR_CODE) + } + + /** + * Allow usage or removal of hexadecimal color + */ + val allowHexColor: Boolean + get() { + 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 + */ + val useColorCost: Int + get() { + if (!isAppend) throw IllegalStateException("Can only call with an append edit type") + return CONFIG + .config + .getInt("${rootPath}.$USE_COLOR_COST", DEFAULT_USE_COLOR_COST) + .takeIf { it in USE_COLOR_COST_RANGE } + ?: DEFAULT_USE_COLOR_COST + + } + + /** + * Cost when using either color code and hex color on lore remove + */ + val removeColorCost: Int + get() { + if (isAppend) throw IllegalStateException("Can only call with a remove edit type") + return CONFIG + .config + .getInt("${rootPath}.$REMOVE_COLOR_COST", DEFAULT_REMOVE_COLOR_COST) + .takeIf { it in REMOVE_COLOR_COST_RANGE } + ?: DEFAULT_REMOVE_COLOR_COST + + } + +} \ No newline at end of file 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 aa09cf4..7d6e396 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,3 +1,21 @@ +# +# 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: @@ -50,10 +68,56 @@ unit_repair_cost: 1 # Valid values include 0 to 1000 sacrifice_illegal_enchant_cost: 1 -# Default limit to apply to any enchants missing from override_limits +# Allow using color code and hexadecimal color. # -# Valid values include 1 to 1000 -default_limit: 5 +# 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 # @@ -61,48 +125,49 @@ 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: - aqua_affinity: 1 - binding_curse: 1 - channeling: 1 - flame: 1 - infinity: 1 - mending: 1 - multishot: 1 - silk_touch: 1 - vanishing_curse: 1 - depth_strider: 3 # anything more than 3 is treated as 3 by the game - protection: 4 - fire_protection: 4 - blast_protection: 4 - projectile_protection: 4 - feather_falling: 4 - thorns: 3 - respiration: 3 - sharpness: 5 - smite: 5 - bane_of_arthropods: 5 - knockback: 2 - fire_aspect: 2 - looting: 3 - sweeping: 3 - sweeping_edge: 3 - efficiency: 5 - unbreaking: 3 - fortune: 3 - power: 5 - punch: 2 - luck_of_the_sea: 3 - lure: 3 - frost_walker: 2 - impaling: 5 - riptide: 3 - loyalty: 3 - piercing: 4 - quick_charge: 3 - soul_speed: 3 - swift_sneak: 3 + 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 # Multipliers used to calculate the enchantment's value in repair/combining # @@ -116,131 +181,289 @@ enchant_limits: # 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: - aqua_affinity: + minecraft:aqua_affinity: item: 4 book: 2 - bane_of_arthropods: + minecraft:bane_of_arthropods: item: 2 book: 1 - binding_curse: + minecraft:binding_curse: item: 8 book: 4 - blast_protection: + minecraft:blast_protection: item: 4 book: 2 - channeling: + minecraft:channeling: item: 8 book: 4 - depth_strider: + minecraft:depth_strider: item: 4 book: 2 - efficiency: + minecraft:efficiency: item: 1 book: 1 - flame: + minecraft:flame: item: 4 book: 2 - feather_falling: + minecraft:feather_falling: item: 2 book: 1 - fire_aspect: + minecraft:fire_aspect: item: 4 book: 2 - fire_protection: + minecraft:fire_protection: item: 2 book: 1 - fortune: + minecraft:fortune: item: 4 book: 2 - frost_walker: + minecraft:frost_walker: item: 4 book: 2 - impaling: + minecraft:impaling: item: 4 book: 2 - infinity: + minecraft:infinity: item: 8 book: 4 - knockback: + minecraft:knockback: item: 2 book: 1 - looting: + minecraft:looting: item: 4 book: 2 - loyalty: + minecraft:loyalty: item: 1 book: 1 - luck_of_the_sea: + minecraft:luck_of_the_sea: item: 4 book: 2 - lure: + minecraft:lure: item: 4 book: 2 - mending: + minecraft:mending: item: 4 book: 2 - multishot: + minecraft:multishot: item: 4 book: 2 - piercing: + minecraft:piercing: item: 1 book: 1 - power: + minecraft:power: item: 1 book: 1 - projectile_protection: + minecraft:projectile_protection: item: 2 book: 1 - protection: + minecraft:protection: item: 1 book: 1 - punch: + minecraft:punch: item: 4 book: 2 - quick_charge: + minecraft:quick_charge: item: 2 book: 1 - respiration: + minecraft:respiration: item: 4 book: 2 - riptide: + minecraft:riptide: item: 4 book: 2 - silk_touch: + minecraft:silk_touch: item: 8 book: 4 - sharpness: + minecraft:sharpness: item: 1 book: 1 - smite: + minecraft:smite: item: 2 book: 1 - soul_speed: + minecraft:soul_speed: item: 8 book: 4 - swift_sneak: + minecraft:swift_sneak: item: 8 book: 4 - sweeping: + minecraft:sweeping: item: 4 book: 2 - sweeping_edge: + minecraft:sweeping_edge: item: 4 book: 2 - thorns: + minecraft:thorns: item: 8 book: 4 - unbreaking: + minecraft:unbreaking: item: 2 book: 1 - vanishing_curse: + minecraft:vanishing_curse: item: 8 book: 4 +# 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) + 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 # Whether to show verbose debug logging debug_log_verbose: false -configVersion: 1.4.5 +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 387fc90..45d62c3 100644 --- a/src/main/resources/enchant_conflict.yml +++ b/src/main/resources/enchant_conflict.yml @@ -1,3 +1,8 @@ +# +# 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: @@ -12,158 +17,162 @@ # ---------------------------------------------------- restriction_aqua_affinity: - enchantments: [ aqua_affinity ] + enchantments: [ minecraft:aqua_affinity ] notAffectedGroups: [ enchanted_book, helmets ] restriction_bane_of_arthropods: - enchantments: [ bane_of_arthropods ] + enchantments: [ minecraft:bane_of_arthropods ] notAffectedGroups: [ enchanted_book, melee_weapons ] restriction_blast_protection: - enchantments: [ blast_protection ] + enchantments: [ minecraft:blast_protection ] notAffectedGroups: [ enchanted_book, armors ] restriction_channeling: - enchantments: [ channeling ] + enchantments: [ minecraft:channeling ] notAffectedGroups: [ enchanted_book, trident ] restriction_binding_curse: - enchantments: [ binding_curse ] + enchantments: [ minecraft:binding_curse ] notAffectedGroups: [ enchanted_book, wearable ] restriction_vanishing_curse: - enchantments: [ vanishing_curse ] + enchantments: [ minecraft:vanishing_curse ] notAffectedGroups: [ enchanted_book, can_vanish ] restriction_depth_strider: - enchantments: [ depth_strider ] + enchantments: [ minecraft:depth_strider ] notAffectedGroups: [ enchanted_book, boots ] restriction_efficiency: - enchantments: [ efficiency ] + enchantments: [ minecraft:efficiency ] notAffectedGroups: [ enchanted_book, tools, shears ] restriction_feather_falling: - enchantments: [ feather_falling ] + enchantments: [ minecraft:feather_falling ] notAffectedGroups: [ enchanted_book, boots ] restriction_fire_aspect: - enchantments: [ fire_aspect ] + enchantments: [ minecraft:fire_aspect ] notAffectedGroups: [ enchanted_book, swords ] restriction_fire_protection: - enchantments: [ fire_protection ] + enchantments: [ minecraft:fire_protection ] notAffectedGroups: [ enchanted_book, armors ] restriction_flame: - enchantments: [ flame ] + enchantments: [ minecraft:flame ] notAffectedGroups: [ enchanted_book, bow ] restriction_fortune: - enchantments: [ fortune ] + enchantments: [ minecraft:fortune ] notAffectedGroups: [ enchanted_book, tools ] restriction_frost_walker: - enchantments: [ frost_walker ] + enchantments: [ minecraft:frost_walker ] notAffectedGroups: [ enchanted_book, boots ] restriction_impaling: - enchantments: [ impaling ] + enchantments: [ minecraft:impaling ] notAffectedGroups: [ enchanted_book, trident ] restriction_infinity: - enchantments: [ infinity ] + enchantments: [ minecraft:infinity ] notAffectedGroups: [ enchanted_book, bow ] restriction_knockback: - enchantments: [ knockback ] + enchantments: [ minecraft:knockback ] notAffectedGroups: [ enchanted_book, swords ] restriction_looting: - enchantments: [ looting ] + enchantments: [ minecraft:looting ] notAffectedGroups: [ enchanted_book, swords ] restriction_loyalty: - enchantments: [ 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: [ lure ] + enchantments: [ minecraft:lure ] notAffectedGroups: [ enchanted_book, fishing_rod ] restriction_mending: - enchantments: [ mending ] + enchantments: [ minecraft:mending ] notAffectedGroups: [ enchanted_book, can_unbreak ] -restriction_multishot: - enchantments: [ multishot ] +restriction_minecraft_multishot: + enchantments: [ minecraft:multishot ] notAffectedGroups: [ enchanted_book, crossbow ] restriction_piercing: - enchantments: [ piercing ] + enchantments: [ minecraft:piercing ] notAffectedGroups: [ enchanted_book, crossbow ] restriction_power: - enchantments: [ power ] + enchantments: [ minecraft:power ] notAffectedGroups: [ enchanted_book, bow ] restriction_projectile_protection: - enchantments: [ projectile_protection ] + enchantments: [ minecraft:projectile_protection ] notAffectedGroups: [ enchanted_book, armors ] restriction_protection: - enchantments: [ protection ] + enchantments: [ minecraft:protection ] notAffectedGroups: [ enchanted_book, armors ] restriction_punch: - enchantments: [ punch ] + enchantments: [ minecraft:punch ] notAffectedGroups: [ enchanted_book, bow ] restriction_quick_charge: - enchantments: [ quick_charge ] + enchantments: [ minecraft:quick_charge ] notAffectedGroups: [ enchanted_book, crossbow ] restriction_respiration: - enchantments: [ respiration ] + enchantments: [ minecraft:respiration ] notAffectedGroups: [ enchanted_book, helmets ] restriction_riptide: - enchantments: [ riptide ] + enchantments: [ minecraft:riptide ] notAffectedGroups: [ enchanted_book, trident ] restriction_sharpness: - enchantments: [ sharpness ] + enchantments: [ minecraft:sharpness ] notAffectedGroups: [ enchanted_book, melee_weapons ] -restriction_silk_touch: - enchantments: [ silk_touch ] +restriction__silk_touch: + enchantments: [ minecraft:silk_touch ] notAffectedGroups: [ enchanted_book, tools ] restriction_smite: - enchantments: [ smite ] + enchantments: [ minecraft:smite ] notAffectedGroups: [ enchanted_book, melee_weapons ] restriction_soul_speed: - enchantments: [ soul_speed ] + enchantments: [ minecraft:soul_speed ] notAffectedGroups: [ enchanted_book, boots ] restriction_sweeping_edge: - enchantments: [ sweeping, 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: [ swift_sneak ] + enchantments: [ minecraft:swift_sneak ] notAffectedGroups: [ enchanted_book, leggings ] restriction_thorns: - enchantments: [ thorns ] + enchantments: [ minecraft:thorns ] notAffectedGroups: [ enchanted_book, armors ] -restriction_unbreaking: - enchantments: [ unbreaking ] +restriction__unbreaking: + enchantments: [ minecraft:unbreaking ] notAffectedGroups: [ enchanted_book, can_unbreak ] # ---------------------------------------------------- @@ -175,60 +184,60 @@ restriction_unbreaking: sword_enchant_conflict: enchantments: - - bane_of_arthropods - - smite - - sharpness + - minecraft:bane_of_arthropods + - minecraft:smite + - minecraft:sharpness notAffectedGroups: [ ] maxEnchantmentBeforeConflict: 1 protection_enchant_conflict: enchantments: - - blast_protection - - fire_protection - - projectile_protection - - protection + - minecraft:blast_protection + - minecraft:fire_protection + - minecraft:projectile_protection + - minecraft:protection notAffectedGroups: [ ] maxEnchantmentBeforeConflict: 1 trident_conflict1: enchantments: - - channeling - - riptide + - minecraft:channeling + - minecraft:riptide notAffectedGroups: [ ] maxEnchantmentBeforeConflict: 1 trident_conflict2: enchantments: - - loyalty - - riptide + - minecraft:loyalty + - minecraft:riptide notAffectedGroups: [ ] maxEnchantmentBeforeConflict: 1 boot_conflict: enchantments: - - depth_strider - - frost_walker + - minecraft:depth_strider + - minecraft:frost_walker notAffectedGroups: [ ] maxEnchantmentBeforeConflict: 1 tool_conflict: enchantments: - - fortune - - silk_touch + - minecraft:fortune + - minecraft:silk_touch notAffectedGroups: [ ] maxEnchantmentBeforeConflict: 1 bow_conflict: enchantments: - - mending - - infinity + - minecraft:mending + - minecraft:infinity notAffectedGroups: [ ] maxEnchantmentBeforeConflict: 1 crossbow_conflict: enchantments: - - multishot - - piercing + - minecraft:multishot + - minecraft:piercing notAffectedGroups: [ ] maxEnchantmentBeforeConflict: 1 diff --git a/src/main/resources/item_groups.yml b/src/main/resources/item_groups.yml index 2e222aa..8a62f3d 100644 --- a/src/main/resources/item_groups.yml +++ b/src/main/resources/item_groups.yml @@ -1,3 +1,8 @@ +# +# 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: @@ -121,7 +126,7 @@ wearable: groups: - armors -tools: +pickaxes: type: include items: - wooden_pickaxe @@ -130,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 b824e5f..dab9997 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,16 +1,19 @@ main: io.delilaheve.CustomAnvil name: CustomAnvil prefix: "Custom Anvil" -version: 1.5.0-beta +version: ${version} +folia-supported: true description: Allow to customise anvil mechanics api-version: 1.16 load: POSTWORLD authors: [ DelilahEve, alexcrea ] -libraries: - - org.jetbrains.kotlin:kotlin-stdlib:1.6.21 - - com.github.stefvanschie.inventoryframework:IF:0.10.14 +libraries: [${libraries}] commands: + customanvil: + description: Generic command for custom anvil + aliases: + - ca anvilconfigreload: description: Reload every config of this plugin permission: ca.command.reload @@ -37,13 +40,44 @@ 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 + # color permissions + ca.color.code: + default: op + description: Allow player to use color code if enabled (toggleable) + 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 + description: Allow player to edit lore via book and quil if enabled (toggleable) + 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, so I can disable it if it is on the same server -# as it is the 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 - ProtocolLib + - Disenchantment - EnchantsSquared + - EcoEnchants + - eco + - ExcellentEnchants + - HavenBags + - SuperEnchants diff --git a/src/main/resources/unit_repair_item.yml b/src/main/resources/unit_repair_item.yml index 0e96878..2902cce 100644 --- a/src/main/resources/unit_repair_item.yml +++ b/src/main/resources/unit_repair_item.yml @@ -1,3 +1,8 @@ +# +# 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 diff --git a/src/test/java/io/delilaheve/CustomAnvilTests.java b/src/test/java/io/delilaheve/CustomAnvilTests.java new file mode 100644 index 0000000..12e1ec1 --- /dev/null +++ b/src/test/java/io/delilaheve/CustomAnvilTests.java @@ -0,0 +1,22 @@ +package io.delilaheve; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import xyz.alexcrea.cuanvil.tests.DefaultCustomAnvilTest; + +public class CustomAnvilTests extends DefaultCustomAnvilTest { + + @Test + public void simpleInitTest() { + try { + Assertions.assertNotNull(server); + Assertions.assertNotNull(plugin); + + // 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 new file mode 100644 index 0000000..10ea252 --- /dev/null +++ b/src/test/java/io/delilaheve/util/EnchantmentUtilTests.java @@ -0,0 +1,184 @@ +package io.delilaheve.util; + +import io.delilaheve.CustomAnvil; +import org.bukkit.Material; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.AnvilInventory; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.permissions.PermissionAttachment; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +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.tests.ConfigResetCustomAnvilTest; +import xyz.alexcrea.cuanvil.data.AnvilFuseTestData; +import xyz.alexcrea.cuanvil.util.AnvilFuseTestUtil; + +import java.util.List; + +public class EnchantmentUtilTests extends ConfigResetCustomAnvilTest { + + private AnvilInventory anvil; + private PlayerMock player; + + @Override + @BeforeEach + public void setUp() { + super.setUp(); + // Mock used player & open anvil + player = server.addPlayer(); + + Inventory anvil = server.createInventory(player, InventoryType.ANVIL); + + this.anvil = (AnvilInventory) anvil; + player.openInventory(anvil); + + ConfigHolder.DEFAULT_CONFIG.getConfig().set("debug_log", true); + ConfigHolder.DEFAULT_CONFIG.getConfig().set("debug_log_verbose", true); + } + + @Test + public void testBypassFuse(){ + // Test permission did not changed (if it do then server owner should be warned) + String permission = CustomAnvil.bypassFusePermission; + Assertions.assertEquals("ca.bypass.fuse", permission, "bypass fuse permission changed. " + + "Caution with that as it will break some server CustomAnvil setup."); + + // Create ingredient item + ItemStack normalStick = new ItemStackMock(Material.STICK); + ItemStack sharpnessBook = AnvilFuseTestUtil.prepareItem( + Material.ENCHANTED_BOOK, + List.of("sharpness"), 1); + + ItemStack sharpnessStick = AnvilFuseTestUtil.prepareItem( + Material.STICK, + List.of("sharpness"), 1); + + // Create result item + ItemStack sharpnessResultStick = AnvilFuseTestUtil.prepareItem( + Material.STICK, 1, + List.of("sharpness"), 1); + ItemStack sharpness2ResultStick = AnvilFuseTestUtil.prepareItem( + Material.STICK, 1, + List.of("sharpness"), 2); + + // Create failing anvil fuse data + AnvilFuseTestData nullResultData = new AnvilFuseTestData( + normalStick, sharpnessBook, + null + ); + AnvilFuseTestData nullResultData2 = new AnvilFuseTestData( + sharpnessStick, sharpnessStick, + null + ); + + // Create successful anvil fuse data + AnvilFuseTestData legalResultData = new AnvilFuseTestData( + normalStick, sharpnessBook, + sharpnessResultStick + // TODO add expected price + ); + AnvilFuseTestData legalResultData2 = new AnvilFuseTestData( + sharpnessStick, sharpnessStick, + sharpness2ResultStick + // TODO add expected price + ); + + // Test with no permission + nullResultData.executeTest(anvil, player); + nullResultData2.executeTest(anvil, player); + + // Add permission + PermissionAttachment attachment = player.addAttachment(plugin); + attachment.setPermission(permission, true); + + // Test with new permission + legalResultData.executeTest(anvil, player); + legalResultData2.executeTest(anvil, player); + } + + @Test + public void testLeveLimitFuse(){ + String permission = CustomAnvil.bypassLevelPermission; + Assertions.assertEquals("ca.bypass.level", permission, "level fuse permission changed. " + + "Caution with that as it will break some server CustomAnvil setup."); + + // Create ingredient item + ItemStack sharpness5Sword = AnvilFuseTestUtil.prepareItem( + Material.DIAMOND_SWORD, + List.of("sharpness"), 5); + + ItemStack sharpnessBook = AnvilFuseTestUtil.prepareItem( + Material.ENCHANTED_BOOK, + List.of("sharpness"), 1); + ItemStack sharpness5Book = AnvilFuseTestUtil.prepareItem( + Material.ENCHANTED_BOOK, + List.of("sharpness"), 5); + ItemStack sharpness6Book = AnvilFuseTestUtil.prepareItem( + Material.ENCHANTED_BOOK, + List.of("sharpness"), 6); + + // Create result item + ItemStack sharpness2BookResult = AnvilFuseTestUtil.prepareItem( + Material.ENCHANTED_BOOK, 1, + List.of("sharpness"), 2); + ItemStack sharpness6SwordResult = AnvilFuseTestUtil.prepareItem( + Material.DIAMOND_SWORD, 1, + List.of("sharpness"), 6); + + // Create failing anvil fuse data + AnvilFuseTestData nullResultData = new AnvilFuseTestData( + sharpnessBook, sharpnessBook, + null + ); + AnvilFuseTestData nullResultData2 = new AnvilFuseTestData( + sharpness5Sword, sharpness6Book, + null + ); + AnvilFuseTestData nullResultData3 = new AnvilFuseTestData( + sharpness5Sword, sharpness5Book, + null + ); + + // Create successful anvil fuse data + AnvilFuseTestData legalResultData = new AnvilFuseTestData( + sharpnessBook, sharpnessBook, + sharpness2BookResult + // TODO add expected price + ); + AnvilFuseTestData legalResultData2 = new AnvilFuseTestData( + sharpness5Sword, sharpness6Book, + sharpness6SwordResult + // TODO add expected price + ); + AnvilFuseTestData legalResultData3 = new AnvilFuseTestData( + sharpness5Sword, sharpness5Book, + sharpness6SwordResult + // TODO add expected price + ); + + // Test failing result first + nullResultData2.executeTest(anvil, player); + nullResultData3.executeTest(anvil, player); + + // Test working sharpness 2 + legalResultData.executeTest(anvil, player); + + // Set merge limit to 2 & test + ConfigHolder.DEFAULT_CONFIG.getConfig().set("disable-merge-over.minecraft:sharpness", 1); + nullResultData.executeTest(anvil, player); + + // Add permission + PermissionAttachment attachment = player.addAttachment(plugin); + attachment.setPermission(permission, true); + + // Test working sharpness 2 + 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 new file mode 100644 index 0000000..19f1d6f --- /dev/null +++ b/src/test/java/xyz/alexcrea/cuanvil/anvil/AnvilFuseTests.java @@ -0,0 +1,126 @@ +package xyz.alexcrea.cuanvil.anvil; + +import io.delilaheve.util.ConfigOptions; +import net.kyori.adventure.text.Component; +import org.bukkit.Material; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.AnvilInventory; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.Repairable; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockbukkit.mockbukkit.entity.PlayerMock; +import xyz.alexcrea.cuanvil.config.ConfigHolder; +import xyz.alexcrea.cuanvil.tests.SharedCustomAnvilTest; +import xyz.alexcrea.cuanvil.data.AnvilFuseTestData; +import xyz.alexcrea.cuanvil.util.AnvilFuseTestUtil; +import xyz.alexcrea.cuanvil.util.CommonItemUtil; + +public class AnvilFuseTests extends SharedCustomAnvilTest { + + private static AnvilInventory anvil; + private static PlayerMock player; + + @BeforeAll + public static void setUp() { + // Mock used player & open anvil + player = server.addPlayer(); + + Inventory anvil = server.createInventory(player, InventoryType.ANVIL); + + AnvilFuseTests.anvil = (AnvilInventory) anvil; + player.openInventory(anvil); + + ConfigHolder.DEFAULT_CONFIG.getConfig().set(ConfigOptions.DEBUG_LOGGING, true); + ConfigHolder.DEFAULT_CONFIG.getConfig().set(ConfigOptions.VERBOSE_DEBUG_LOGGING, true); + ConfigHolder.DEFAULT_CONFIG.getConfig().set(ConfigOptions.ALLOW_COLOR_CODE, true); // For rename test + } + + @BeforeEach + public void prepareAnvil(){ + anvil.clear(); + } + + @AfterAll + public static void tearDown() { + player = null; + anvil = null; + } + + @Test + public void mergeFuseTest(){ + // Literally just test a sharpness 4 + sharpness 4 + ItemStack sharpness4 = CommonItemUtil.sharpness(4); + + ItemStack sharpness5Result = CommonItemUtil.sharpness(5); + Repairable meta = (Repairable) sharpness5Result.getItemMeta(); + meta.setRepairCost(1); + sharpness5Result.setItemMeta(meta); + + AnvilFuseTestData data = new AnvilFuseTestData( + sharpness4, sharpness4, + sharpness5Result, + 5 + ); + + data.executeTest(anvil, player); + } + + @Test + public void overFuseTest(){ + // Test sharpness 4 + sharpness 5 + ItemStack sharpness4 = CommonItemUtil.sharpness(4); + ItemStack sharpness5 = CommonItemUtil.sharpness(5); + + ItemStack sharpness5Result = CommonItemUtil.sharpness(5); + Repairable meta = (Repairable) sharpness5Result.getItemMeta(); + meta.setRepairCost(1); + sharpness5Result.setItemMeta(meta); + + AnvilFuseTestData data = new AnvilFuseTestData( + sharpness4, sharpness5, + sharpness5Result, + 5 + ); + + data.executeTest(anvil, player); + } + + @Test + public void underFuseTest(){ + // test sharpness 5 + 4. Custom Anvil should not allow it to be as it result as the same item as left item + ItemStack sharpness4 = CommonItemUtil.sharpness(4); + ItemStack sharpness5 = CommonItemUtil.sharpness(5); + + AnvilFuseTestData data = new AnvilFuseTestData( + sharpness5, sharpness4, + null + ); + + data.executeTest(anvil, player); + } + + // Note: currently anvil can only have null name. maybe handle differently later + @Test + public void nullNameResetTest(){ + ItemStack base = new ItemStack(Material.NETHERITE_SWORD); + ItemStack expected = base.clone(); + + ItemMeta meta = expected.getItemMeta(); + meta.displayName(Component.text("test")); + base.setItemMeta(meta); + + AnvilFuseTestData data = new AnvilFuseTestData( + base, null, + expected, expected, null, + 1, null, 1 + ); + + 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 new file mode 100644 index 0000000..668da58 --- /dev/null +++ b/src/test/java/xyz/alexcrea/cuanvil/anvil/LoreEditTests.java @@ -0,0 +1,622 @@ +package xyz.alexcrea.cuanvil.anvil; + +import io.delilaheve.util.ConfigOptions; +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; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.BookMeta; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.junit.jupiter.api.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockbukkit.mockbukkit.entity.PlayerMock; +import xyz.alexcrea.cuanvil.config.ConfigHolder; +import xyz.alexcrea.cuanvil.data.AnvilClickTestData; +import xyz.alexcrea.cuanvil.data.AnvilFuseTestData; +import xyz.alexcrea.cuanvil.data.TestDataContainer; +import xyz.alexcrea.cuanvil.tests.SharedCustomAnvilTest; +import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil; +import xyz.alexcrea.cuanvil.util.config.LoreEditType; + +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 PlayerMock player; + + private static final String COLORED_LORE_LINE = "§x§1§2§3§4§5§6TEST §atest"; + private static final String UNCOLORED_LORE_LINE = "#123456TEST &atest"; + + private static final int COLOR_USE_COST = 1; + private static final int COLOR_REMOVE_COST = 2; + private static final int NON_ONE_TEST_FIXED_COST = 10; + private static final int NON_ZERO_TEST_LINE_COST = 2; + + private static ItemStack emptyItem; + private static ItemStack oneColoredLoreItem; + private static ItemStack twoColoredLoreItem; + private static ItemStack oneUncoloredLoreItem; + private static ItemStack twoUncoloredLoreItem; + + private static ItemStack emptyPaperStack; + private static ItemStack emptyPaper63Item; + private static ItemStack emptyPaperOne; + private static ItemStack coloredPaperStack; + private static ItemStack coloredPaper63Item; + private static ItemStack coloredPaperOne; + private static ItemStack uncoloredPaperStack; + private static ItemStack uncoloredPaper63Item; + private static ItemStack uncoloredPaperOne; + + private static ItemStack emptyBook; + private static ItemStack coloredBook1Line; + private static ItemStack coloredBook2Line; + private static ItemStack uncoloredBook1Line; + private static ItemStack uncoloredBook2Line; + + private static TestDataContainer defBookAppend; + private static TestDataContainer defBookRemove; + + private static TestDataContainer defPaperAppend; + private static TestDataContainer defPaperRemove; + + private static TestDataContainer defMultilineBookAppend; + private static TestDataContainer defMultilineBookRemove; + + private static TestDataContainer defMultilinePaperAppend; + private static TestDataContainer defMultilinePaperRemove; + + private static Map singleLineTypeToTest; + private static Map multiLineTypeToTest; + + @BeforeAll + public static void setUp() { + // Mock used player & open anvil + player = server.addPlayer(); + + Inventory anvil = server.createInventory(player, InventoryType.ANVIL); + + LoreEditTests.anvil = (AnvilInventory) anvil; + player.openInventory(anvil); + + ConfigHolder.DEFAULT_CONFIG.getConfig().set(ConfigOptions.DEBUG_LOGGING, true); + ConfigHolder.DEFAULT_CONFIG.getConfig().set(ConfigOptions.VERBOSE_DEBUG_LOGGING, true); + + // Applied item + ItemStack item = new ItemStack(Material.STICK, 33); + ItemMeta meta = item.getItemMeta(); + ArrayList lore = new ArrayList<>(); + + emptyItem = item.clone(); + + lore.add(COLORED_LORE_LINE); + meta.setLore(lore); + item.setItemMeta(meta); + oneColoredLoreItem = item.clone(); + + lore.add(COLORED_LORE_LINE); + meta.setLore(lore); + item.setItemMeta(meta); + twoColoredLoreItem = item.clone(); + + lore.clear(); + lore.add(UNCOLORED_LORE_LINE); + meta.setLore(lore); + item.setItemMeta(meta); + oneUncoloredLoreItem = item.clone(); + + lore.add(UNCOLORED_LORE_LINE); + meta.setLore(lore); + item.setItemMeta(meta); + twoUncoloredLoreItem = item.clone(); + lore.clear(); + + // Paper items + item = new ItemStack(Material.PAPER, 64); + meta = item.getItemMeta(); + emptyPaperStack = item.clone(); + item.setAmount(63); + emptyPaper63Item = item.clone(); + item.setAmount(1); + emptyPaperOne = item.clone(); + + item.setAmount(64); + meta.setDisplayName(COLORED_LORE_LINE); + item.setItemMeta(meta); + coloredPaperStack = item.clone(); + item.setAmount(63); + coloredPaper63Item = item.clone(); + item.setAmount(1); + coloredPaperOne = item.clone(); + + item.setAmount(64); + meta.setDisplayName(UNCOLORED_LORE_LINE); + item.setItemMeta(meta); + uncoloredPaperStack = item.clone(); + item.setAmount(63); + uncoloredPaper63Item = item.clone(); + item.setAmount(1); + uncoloredPaperOne = item.clone(); + + // Book items + item = new ItemStack(Material.WRITABLE_BOOK); + BookMeta bookmeta = (BookMeta) item.getItemMeta(); + emptyBook = item.clone(); + + bookmeta.setPages(COLORED_LORE_LINE); + item.setItemMeta(bookmeta); + coloredBook1Line = item.clone(); + + bookmeta.setPages(COLORED_LORE_LINE + "\n" + COLORED_LORE_LINE); + item.setItemMeta(bookmeta); + coloredBook2Line = item.clone(); + + bookmeta.setPages(UNCOLORED_LORE_LINE); + item.setItemMeta(bookmeta); + uncoloredBook1Line = item.clone(); + + bookmeta.setPages(UNCOLORED_LORE_LINE + "\n" + UNCOLORED_LORE_LINE); + item.setItemMeta(bookmeta); + uncoloredBook2Line = item.clone(); + + // Default working test data + defBookAppend = new TestDataContainer( + new AnvilFuseTestData( + emptyItem, uncoloredBook1Line, + oneColoredLoreItem, + 1 + ), new AnvilClickTestData( + null, emptyBook, null, oneColoredLoreItem, + 1, Event.Result.DENY, + true, Event.Result.DENY + )); + + defBookRemove = new TestDataContainer( + new AnvilFuseTestData( + oneColoredLoreItem, emptyBook, + emptyItem, + 1 + ), new AnvilClickTestData( + null, coloredBook1Line, null, emptyItem, + 1, Event.Result.DENY, + true, Event.Result.DENY + )); + + defPaperAppend = new TestDataContainer( + new AnvilFuseTestData( + emptyItem, uncoloredPaperStack, + oneColoredLoreItem, + 1 + ), new AnvilClickTestData( + emptyPaperOne, uncoloredPaper63Item, null, oneColoredLoreItem, + 1, Event.Result.DENY, + true, Event.Result.DENY + )); + + defPaperRemove = new TestDataContainer( + new AnvilFuseTestData( + oneColoredLoreItem, emptyPaperStack, + emptyItem, + 1 + ), new AnvilClickTestData( + coloredPaperOne, emptyPaper63Item, null, emptyItem, + 1, Event.Result.DENY, + true, Event.Result.DENY + )); + defMultilineBookAppend = new TestDataContainer( + new AnvilFuseTestData( + emptyItem, uncoloredBook2Line, + twoColoredLoreItem, + 1 + ), new AnvilClickTestData( + null, emptyBook, null, twoColoredLoreItem, + 1, Event.Result.DENY, + true, Event.Result.DENY + )); + + defMultilineBookRemove = new TestDataContainer( + new AnvilFuseTestData( + twoColoredLoreItem, emptyBook, + emptyItem, + 1 + ), new AnvilClickTestData( + null, coloredBook2Line, null, emptyItem, + 1, Event.Result.DENY, + true, Event.Result.DENY + )); + + defMultilinePaperAppend = new TestDataContainer( + new AnvilFuseTestData( + oneColoredLoreItem, uncoloredPaperStack, + twoColoredLoreItem, + 1 + ), new AnvilClickTestData( + emptyPaperOne, uncoloredPaper63Item, null, twoColoredLoreItem, + 1, Event.Result.DENY, + true, Event.Result.DENY + )); + + defMultilinePaperRemove = new TestDataContainer( + new AnvilFuseTestData( + twoColoredLoreItem, emptyPaperStack, + oneColoredLoreItem, + 1 + ), new AnvilClickTestData( + coloredPaperOne, emptyPaper63Item, null, oneColoredLoreItem, + 1, Event.Result.DENY, + true, Event.Result.DENY + )); + + singleLineTypeToTest = Map.of( + LoreEditType.APPEND_BOOK, defBookAppend, + LoreEditType.REMOVE_BOOK, defBookRemove, + LoreEditType.APPEND_PAPER, defPaperAppend, + LoreEditType.REMOVE_PAPER, defPaperRemove + ); + + multiLineTypeToTest = Map.of( + LoreEditType.APPEND_BOOK, defMultilineBookAppend, + LoreEditType.REMOVE_BOOK, defMultilineBookRemove, + LoreEditType.APPEND_PAPER, defMultilinePaperAppend, + LoreEditType.REMOVE_PAPER, defMultilinePaperRemove + ); + } + + @BeforeEach + public void prepareAnvil() { + anvil.clear(); + + // Make sure we reset value in case it got modified + for (@NotNull LoreEditType type : LoreEditType.values()) { + // Make sure it is enabled for the tests (unless its is enabled test) + ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.IS_ENABLED, true); + + ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.DO_CONSUME, false); + ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.FIXED_COST, 1); + ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.PER_LINE_COST, 0); + + // Make sur color is enabled by default + 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_COST, 0); + } + + } + + // Disable them by default and test them on specific tests + ConfigHolder.DEFAULT_CONFIG.getConfig().set(LoreEditConfigUtil.BOOK_PERMISSION_NEEDED, false); + ConfigHolder.DEFAULT_CONFIG.getConfig().set(LoreEditConfigUtil.PAPER_PERMISSION_NEEDED, false); + + ConfigHolder.DEFAULT_CONFIG.getConfig().set(LoreEditConfigUtil.PAPER_EDIT_ORDER, LoreEditConfigUtil.DEFAULT_PAPER_EDIT_ORDER); + } + + @AfterAll + public static void tearDown() { + player = null; + anvil = null; + } + + public @Nullable ItemStack uncoloredEquivalent(@Nullable ItemStack colored) { + // null check + if (null == colored) return null; + + if (oneColoredLoreItem == colored) return oneUncoloredLoreItem; + if (twoColoredLoreItem == colored) return twoUncoloredLoreItem; + + if (coloredPaperStack == colored) return uncoloredPaperStack; + if (coloredPaper63Item == colored) return uncoloredPaper63Item; + if (coloredPaperOne == colored) return uncoloredPaperOne; + + if (coloredBook1Line == colored) return uncoloredBook1Line; + if (coloredBook2Line == colored) return uncoloredBook2Line; + + // They already are uncolored + if (oneUncoloredLoreItem == colored) return oneUncoloredLoreItem; + if (twoUncoloredLoreItem == colored) return twoUncoloredLoreItem; + + if (uncoloredPaperStack == colored) return uncoloredPaperStack; + if (uncoloredPaper63Item == colored) return uncoloredPaper63Item; + if (uncoloredPaperOne == colored) return uncoloredPaperOne; + + if (uncoloredBook1Line == colored) return uncoloredBook1Line; + if (uncoloredBook2Line == colored) return uncoloredBook2Line; + + // No lore items return themself + if (emptyItem == colored) return emptyItem; + if (emptyBook == colored) return emptyBook; + if (emptyPaperStack == colored) return emptyPaperStack; + if (emptyPaper63Item == colored) return emptyPaper63Item; + if (emptyPaperOne == colored) return emptyPaperOne; + + Assertions.fail("Could not find uncolored version of " + colored); + return null; + } + + public static List onlyAppendTypes() { + ArrayList typeList = new ArrayList<>(); + for (@NotNull LoreEditType type : LoreEditType.values()) { + if (type.isAppend()) typeList.add(type); + } + return typeList; + } + + public static List onlyRemoveTypes() { + ArrayList typeList = new ArrayList<>(); + for (@NotNull LoreEditType type : LoreEditType.values()) { + if (!type.isAppend()) typeList.add(type); + } + return typeList; + } + + @ParameterizedTest + @EnumSource(LoreEditType.class) + public void simpleTest(LoreEditType type) { + // Test all defaults to make sure they works + singleLineTypeToTest.get(type).executeTest(anvil, player); + multiLineTypeToTest.get(type).executeTest(anvil, player); + + } + + @ParameterizedTest + @EnumSource(LoreEditType.class) + public void testPermissionNeeded_DEOP(LoreEditType type) { + ConfigHolder.DEFAULT_CONFIG.getConfig().set(LoreEditConfigUtil.BOOK_PERMISSION_NEEDED, true); + ConfigHolder.DEFAULT_CONFIG.getConfig().set(LoreEditConfigUtil.PAPER_PERMISSION_NEEDED, true); + player.setOp(false); + + singleLineTypeToTest.get(type).nullifyResult().executeTest(anvil, player); + multiLineTypeToTest.get(type).nullifyResult().executeTest(anvil, player); + } + + @ParameterizedTest + @EnumSource(LoreEditType.class) + public void testPermissionNeeded_OP(LoreEditType type) { + ConfigHolder.DEFAULT_CONFIG.getConfig().set(LoreEditConfigUtil.BOOK_PERMISSION_NEEDED, true); + ConfigHolder.DEFAULT_CONFIG.getConfig().set(LoreEditConfigUtil.PAPER_PERMISSION_NEEDED, true); + player.setOp(true); + + singleLineTypeToTest.get(type).executeTest(anvil, player); + multiLineTypeToTest.get(type).executeTest(anvil, player); + } + + @ParameterizedTest + @EnumSource(LoreEditType.class) + public void testLoreTypeDisabled(LoreEditType type) { + ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.IS_ENABLED, false); + + singleLineTypeToTest.get(type).nullifyResult().executeTest(anvil, player); + multiLineTypeToTest.get(type).nullifyResult().executeTest(anvil, player); + } + + @ParameterizedTest + @EnumSource(LoreEditType.class) + public void testFixedCost_Default(LoreEditType type) { + singleLineTypeToTest.get(type).setCost(1).executeTest(anvil, player); + multiLineTypeToTest.get(type).setCost(1).executeTest(anvil, player); + } + + @ParameterizedTest + @EnumSource(LoreEditType.class) + public void testFixedCost_Modified(LoreEditType type) { + ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.FIXED_COST, NON_ONE_TEST_FIXED_COST); + + singleLineTypeToTest.get(type).setCost(NON_ONE_TEST_FIXED_COST).executeTest(anvil, player); + multiLineTypeToTest.get(type).setCost(NON_ONE_TEST_FIXED_COST).executeTest(anvil, player); + } + + @ParameterizedTest + @EnumSource(LoreEditType.class) + public void testLineCost_Modified(LoreEditType type) { + ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.PER_LINE_COST, NON_ZERO_TEST_LINE_COST); + + if (type.isMultiLine()) { + singleLineTypeToTest.get(type).setCost(NON_ZERO_TEST_LINE_COST + LoreEditConfigUtil.DEFAULT_FIXED_COST).executeTest(anvil, player); + multiLineTypeToTest.get(type).setCost(2 * NON_ZERO_TEST_LINE_COST + LoreEditConfigUtil.DEFAULT_FIXED_COST).executeTest(anvil, player); + } else { + singleLineTypeToTest.get(type).executeTest(anvil, player); + multiLineTypeToTest.get(type).executeTest(anvil, player); + } + } + + @ParameterizedTest + @EnumSource(LoreEditType.class) + 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); + + TestDataContainer singleLData = singleLineTypeToTest.get(type); + TestDataContainer multiLData = multiLineTypeToTest.get(type); + + if (type.isAppend()) { + singleLData.setCost(COLOR_USE_COST + LoreEditConfigUtil.DEFAULT_FIXED_COST).executeTest(anvil, player); + multiLData.setCost(COLOR_USE_COST + LoreEditConfigUtil.DEFAULT_FIXED_COST).executeTest(anvil, player); + } else { + singleLData + .setCost(COLOR_REMOVE_COST + LoreEditConfigUtil.DEFAULT_FIXED_COST) + .setClickRight(uncoloredEquivalent(singleLData.getRightClick())) + .setClickLeft(uncoloredEquivalent(singleLData.getLeftClick())) + .executeTest(anvil, player); + + multiLData.setCost(COLOR_REMOVE_COST + LoreEditConfigUtil.DEFAULT_FIXED_COST) + .setClickRight(uncoloredEquivalent(multiLData.getRightClick())) + .setClickLeft(uncoloredEquivalent(multiLData.getLeftClick())) + .executeTest(anvil, player); + } + } + + @ParameterizedTest + @MethodSource("onlyAppendTypes") + public void testColorDisabled(LoreEditType type) { + ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.USE_COLOR_COST, COLOR_USE_COST); + ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.ALLOW_HEX_COLOR, false); + ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.ALLOW_COLOR_CODE, false); + + TestDataContainer singleLData = singleLineTypeToTest.get(type); + TestDataContainer multiLData = multiLineTypeToTest.get(type); + + singleLData + .setExpectedResult(uncoloredEquivalent(singleLData.getExpectedFuse())) + .executeTest(anvil, player); + multiLData + .setFuseLeft(uncoloredEquivalent(multiLData.getLeftFuse())) + .setExpectedResult(uncoloredEquivalent(multiLData.getExpectedFuse())) + .executeTest(anvil, player); + } + + @ParameterizedTest + @MethodSource("onlyRemoveTypes") + public void testColorRemoveEnabled(LoreEditType type) { + TestDataContainer singleLData = singleLineTypeToTest.get(type); + TestDataContainer multiLData = multiLineTypeToTest.get(type); + + singleLData + .setClickRight(uncoloredEquivalent(singleLData.getRightClick())) + .setClickLeft(uncoloredEquivalent(singleLData.getLeftClick())) + .executeTest(anvil, player); + multiLData + .setClickRight(uncoloredEquivalent(multiLData.getRightClick())) + .setClickLeft(uncoloredEquivalent(multiLData.getLeftClick())) + .executeTest(anvil, player); + } + + @ParameterizedTest + @MethodSource("onlyRemoveTypes") + public void testDoConsume(LoreEditType type) { + ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.DO_CONSUME, true); + + TestDataContainer singleLData = singleLineTypeToTest.get(type); + TestDataContainer multiLData = multiLineTypeToTest.get(type); + + // NOTE: we set to null right item only on multi line bc the default data has more than one paper and would go to the left instead + singleLData = singleLData + .setClickRight(type.isMultiLine() ? null : singleLData.getRightClick()) + .setClickLeft(null); + singleLData.executeTest(anvil, player); + multiLData = multiLData + .setClickRight(type.isMultiLine() ? null : singleLData.getRightClick()) + .setClickLeft(null); + multiLData.executeTest(anvil, player); + + // Single paper consumed + if (!type.isMultiLine()) { + singleLData.setFuseRight(emptyPaperOne).setClickRight(null).executeTest(anvil, player); + multiLData.setFuseRight(emptyPaperOne).setClickRight(null).executeTest(anvil, player); + } + } + + private void SinglePaperTestPart(LoreEditType type, TestDataContainer data, ItemStack expectedFuse, ItemStack postFuse) { + ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.DO_CONSUME, false); + data = data.setFuseRight(expectedFuse).setClickLeft(null).setClickRight(postFuse); + data.executeTest(anvil, player); + + ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.DO_CONSUME, true); + data.setClickRight(null).executeTest(anvil, player); + } + + @Test + public void testSinglePaper_Append() { + SinglePaperTestPart(LoreEditType.APPEND_PAPER, + singleLineTypeToTest.get(LoreEditType.APPEND_PAPER), + uncoloredPaperOne, emptyPaperOne); + SinglePaperTestPart(LoreEditType.APPEND_PAPER, + multiLineTypeToTest.get(LoreEditType.APPEND_PAPER), + uncoloredPaperOne, emptyPaperOne); + } + + @Test + public void testSinglePaper_Remove() { + SinglePaperTestPart(LoreEditType.REMOVE_PAPER, + singleLineTypeToTest.get(LoreEditType.REMOVE_PAPER), + emptyPaperOne, coloredPaperOne); + SinglePaperTestPart(LoreEditType.REMOVE_PAPER, + multiLineTypeToTest.get(LoreEditType.REMOVE_PAPER), + emptyPaperOne, coloredPaperOne); + } + + @NotNull + private static ItemStack insertToLore(@NotNull ItemStack item, int index, @NotNull String toAppend) { + item = item.clone(); + ItemMeta meta = item.getItemMeta(); + Assertions.assertNotNull(meta); + Assertions.assertTrue(meta.hasLore()); + + ArrayList lore = new ArrayList<>(meta.getLore()); + lore.add(index, toAppend); + + meta.setLore(lore); + item.setItemMeta(meta); + return item; + } + + @NotNull + private static ItemStack setDisplayedName(@NotNull ItemStack item, @NotNull String name) { + item = item.clone(); + ItemMeta meta = item.getItemMeta(); + Assertions.assertNotNull(meta); + + meta.setDisplayName(name); + item.setItemMeta(meta); + return item; + } + + private static final String TESTED_LORE = "tested_lore"; + + @ParameterizedTest + @ValueSource(strings = {"sTaRt", "eNd"}) + public void testPaperOrder_Append(String order) { + ConfigHolder.DEFAULT_CONFIG.getConfig().set(LoreEditConfigUtil.PAPER_EDIT_ORDER, order); + + ItemStack result = insertToLore(oneColoredLoreItem, "start".equalsIgnoreCase(order) ? 0 : 1, TESTED_LORE); + ItemStack paper = setDisplayedName(emptyPaperOne, TESTED_LORE); + + new TestDataContainer( + new AnvilFuseTestData( + oneColoredLoreItem, paper, result, 1 + ), + new AnvilClickTestData( + null, emptyPaperOne, null, result, + 1, + Event.Result.DENY, true, Event.Result.DENY + ) + ).executeTest(anvil, player); + } + + @ParameterizedTest + @ValueSource(strings = {"sTaRt", "eNd"}) + public void testPaperOrder_Remove(String order) { + ConfigHolder.DEFAULT_CONFIG.getConfig().set(LoreEditConfigUtil.PAPER_EDIT_ORDER, order); + + ItemStack from = insertToLore(oneColoredLoreItem, "start".equalsIgnoreCase(order) ? 0 : 1, TESTED_LORE); + ItemStack paper = setDisplayedName(emptyPaperOne, TESTED_LORE); + + new TestDataContainer( + new AnvilFuseTestData( + from, emptyPaperOne, oneColoredLoreItem, 1 + ), + new AnvilClickTestData( + null, paper, null, oneColoredLoreItem, + 1, + Event.Result.DENY, true, Event.Result.DENY + ) + ).executeTest(anvil, player); + } + + //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 new file mode 100644 index 0000000..d9ff329 --- /dev/null +++ b/src/test/java/xyz/alexcrea/cuanvil/api/AnvilRecipeBuilderTest.java @@ -0,0 +1,107 @@ +package xyz.alexcrea.cuanvil.api; + + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import xyz.alexcrea.cuanvil.tests.SharedOnlyMockBukkit; + +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 + void createBuilder_NoLeftItem(){ + builder.setResultItem(new ItemStack(Material.STICK)); + + assertNull(builder.build()); + } + + @Test + void createBuilder_NoResultItem(){ + builder.setLeftItem(new ItemStack(Material.STICK)); + + assertNull(builder.build()); + } + + @Test + void createBuilder_minimalist(){ + builder.setLeftItem(new ItemStack(Material.STICK)) + .setResultItem(new ItemStack(Material.STICK)); + + assertNotNull(builder.build()); + assertNotNull(builder2.build()); + } + + @Test + void setLeftItem(){ + assertNull(builder.getLeftItem()); + builder.setLeftItem(new ItemStack(Material.STICK)); + assertNotNull(builder.getLeftItem()); + } + + @Test + void setRightItem(){ + assertNull(builder.getRightItem()); + builder.setRightItem(new ItemStack(Material.STICK)); + assertNotNull(builder.getRightItem()); + } + + @Test + void setResultItem(){ + assertNull(builder.getResultItem()); + builder.setResultItem(new ItemStack(Material.STICK)); + assertNotNull(builder.getResultItem()); + } + + @Test + void setXpCostPerCraft(){ + 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(builder2.isExactCount()); + assertTrue(builder2.build().getExactCount()); + builder2.setExactCount(false); + assertFalse(builder2.isExactCount()); + assertFalse(builder2.build().getExactCount()); + } + + @Test + void setName(){ + 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 new file mode 100644 index 0000000..1a9a256 --- /dev/null +++ b/src/test/java/xyz/alexcrea/cuanvil/api/ConflictApiTests.java @@ -0,0 +1,160 @@ +package xyz.alexcrea.cuanvil.api; + +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.AnvilInventory; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockbukkit.mockbukkit.entity.PlayerMock; +import xyz.alexcrea.cuanvil.config.ConfigHolder; +import xyz.alexcrea.cuanvil.enchant.CAEnchantment; +import xyz.alexcrea.cuanvil.group.EnchantConflictGroup; +import xyz.alexcrea.cuanvil.tests.ConfigResetCustomAnvilTest; +import xyz.alexcrea.cuanvil.data.AnvilFuseTestData; +import xyz.alexcrea.cuanvil.util.AnvilFuseTestUtil; +import xyz.alexcrea.cuanvil.util.CommonItemUtil; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ConflictApiTests extends ConfigResetCustomAnvilTest { + + private AnvilInventory anvil; + private PlayerMock player; + + @Override + @BeforeEach + public void setUp() { + super.setUp(); + // Mock used player & open anvil + player = server.addPlayer(); + + Inventory anvil = server.createInventory(player, InventoryType.ANVIL); + + this.anvil = (AnvilInventory) anvil; + player.openInventory(anvil); + + ConfigHolder.DEFAULT_CONFIG.getConfig().set("debug_log", true); + ConfigHolder.DEFAULT_CONFIG.getConfig().set("debug_log_verbose", true); + } + + @Test + public void testConflict() { + ItemStack sharpness1 = CommonItemUtil.sharpness(1); + ItemStack arthropods1 = CommonItemUtil.bane_of_arthropods(1); + ItemStack illegalResult = AnvilFuseTestUtil.prepareItem( + Material.DIAMOND_SWORD, 1, + List.of("bane_of_arthropods", "sharpness"), + 1, 1 + ); + + AnvilFuseTestData nullResultData = new AnvilFuseTestData( + sharpness1, arthropods1, + null + ); + + AnvilFuseTestData legalResultData = new AnvilFuseTestData( + sharpness1, arthropods1, + illegalResult, + 2 + ); + + CAEnchantment sharpness = EnchantmentApi.getByKey(Enchantment.SHARPNESS.getKey()); + Assertions.assertNotNull(sharpness); + + // Testing default conflict (illegal item should not be produced) + nullResultData.executeTest(anvil, player); + + // Try to find & remove conflict + EnchantConflictGroup conflict = findGroup("sword_enchant_conflict"); + Assertions.assertNotNull(conflict, "Could not find conflict."); + + // Test what happen when we remove the conflict (illegal item should be allowed) + ConflictAPI.removeConflict(conflict); + legalResultData.executeTest(anvil, player); + + // We create and add a new conflict + ConflictBuilder builder = new ConflictBuilder("sword_enchant_conflict"); + builder.addEnchantment("bane_of_arthropods").addEnchantment(sharpness); //TODO maybe add "add by bukkit enchantment" + builder.setMaxBeforeConflict(1); + + // Nothing should change as it is not new: it was previously deleted + Assertions.assertFalse(builder.registerIfNew()); + legalResultData.executeTest(anvil, player); + + // Now the conflict should be registered and conflict should exist + Assertions.assertTrue(builder.registerIfAbsent()); + nullResultData.executeTest(anvil, player); + } + + @Test + void writeGroup_Reload() { + String conflictName = "conflict"; + ConflictBuilder builder = new ConflictBuilder(conflictName); + builder.addEnchantment("bane_of_arthropods"); + + // Group not being set should not exist + assertFalse(doGroupExist(conflictName)); + + // Add group and reload + assertTrue(ConflictAPI.writeConflict(builder)); + assertFalse(doGroupExist(conflictName)); + + // Tick so write get reloaded + server.getScheduler().performOneTick(); + + assertTrue(doGroupExist(conflictName)); + } + + @Test + void writeGroup_Empty() { + String conflictName = "conflict"; + ConflictBuilder builder = new ConflictBuilder(conflictName); + + // Group not being set should not exist + assertFalse(doGroupExist(conflictName)); + + // Add group and reload + assertFalse(ConflictAPI.writeConflict(builder)); + assertFalse(doGroupExist(conflictName)); + + // Tick so write get reloaded + server.getScheduler().performOneTick(); + + assertFalse(doGroupExist(conflictName)); + } + + @Test + void writeGroup_InvalidDot() { + String conflictName = "conflict.conflict"; + ConflictBuilder builder = new ConflictBuilder(conflictName); + + // Try write group + assertFalse(ConflictAPI.writeConflict(builder)); + } + + // Maybe move to ConflictApi class ? + private static boolean doGroupExist(@NotNull String groupName) { + return findGroup(groupName) != null; + } + + // Maybe move to ConflictApi class ? + @Nullable + private static EnchantConflictGroup findGroup(@NotNull String groupName) { + for (EnchantConflictGroup enchantConflictGroup : ConflictAPI.getRegisteredConflict()) { + if (groupName.equalsIgnoreCase(enchantConflictGroup.getName())) { + return enchantConflictGroup; + } + } + return null; + } + +} diff --git a/src/test/java/xyz/alexcrea/cuanvil/api/ConflictBuilderTests.java b/src/test/java/xyz/alexcrea/cuanvil/api/ConflictBuilderTests.java new file mode 100644 index 0000000..670ae4e --- /dev/null +++ b/src/test/java/xyz/alexcrea/cuanvil/api/ConflictBuilderTests.java @@ -0,0 +1,115 @@ +package xyz.alexcrea.cuanvil.api; + +import org.bukkit.NamespacedKey; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import xyz.alexcrea.cuanvil.group.IncludeGroup; +import xyz.alexcrea.cuanvil.tests.SharedOnlyMockBukkit; + +import static org.junit.jupiter.api.Assertions.*; + +public class ConflictBuilderTests extends SharedOnlyMockBukkit { + + private ConflictBuilder builder; + + @BeforeEach + public void setup() { + builder = new ConflictBuilder("test"); + } + + @Test + void testDefaults() { + assertNull(builder.getSource()); + assertEquals("an unknown source", builder.getSourceName()); + assertEquals("test", builder.getName()); + + assertTrue(builder.getEnchantmentNames().isEmpty()); + assertTrue(builder.getEnchantmentKeys().isEmpty()); + assertTrue(builder.getExcludedGroupNames().isEmpty()); + + assertEquals(0, builder.getMaxBeforeConflict()); + } + + @Test + void setName() { + assertEquals("test", builder.getName()); + assertEquals(builder, builder.setName("another")); + assertEquals("another", builder.getName()); + } + + @Test + void setMaxBeforeConflict() { + assertEquals(0, builder.getMaxBeforeConflict()); + assertEquals(builder, builder.setMaxBeforeConflict(1)); + assertEquals(1, builder.getMaxBeforeConflict()); + } + + @Test + void enchantmentString() { + assertTrue(builder.getEnchantmentNames().isEmpty()); + assertEquals(builder, builder.addEnchantment("bane_of_arthropods")); + assertEquals(1, builder.getEnchantmentNames().size()); + + assertEquals(builder, builder.removeEnchantment("bane_of_arthropods")); + assertTrue(builder.getEnchantmentNames().isEmpty()); + } + + @Test + void enchantmentKey() { + NamespacedKey key = NamespacedKey.fromString("bane_of_arthropods"); + assertNotNull(key); + + assertTrue(builder.getEnchantmentKeys().isEmpty()); + assertEquals(builder, builder.addEnchantment(key)); + assertEquals(1, builder.getEnchantmentKeys().size()); + + assertEquals(builder, builder.removeEnchantment(key)); + assertTrue(builder.getEnchantmentKeys().isEmpty()); + } + + @Test + void excludedGroup_String() { + assertTrue(builder.getExcludedGroupNames().isEmpty()); + assertEquals(builder, builder.addExcludedGroup("group")); + assertEquals(1, builder.getExcludedGroupNames().size()); + + assertEquals(builder, builder.removeExcludedGroup("group")); + assertTrue(builder.getExcludedGroupNames().isEmpty()); + } + + @Test + void excludedGroup_Group() { + IncludeGroup group = new IncludeGroup("group"); + + assertTrue(builder.getExcludedGroupNames().isEmpty()); + assertEquals(builder, builder.addExcludedGroup(group)); + assertEquals(1, builder.getExcludedGroupNames().size()); + + assertEquals(builder, builder.removeExcludedGroup(group)); + assertTrue(builder.getExcludedGroupNames().isEmpty()); + } + + @Test + void copy(){ + builder.setName("other"); + builder.setMaxBeforeConflict(1); + + builder.addEnchantment("bane_of_arthropods"); + builder.addEnchantment(NamespacedKey.fromString("bane_of_arthropods")); + builder.addExcludedGroup("group"); + builder.addExcludedGroup(new IncludeGroup("group2")); + + ConflictBuilder copy = builder.copy(); + assertEquals("other", copy.getName()); + assertEquals(1, copy.getMaxBeforeConflict()); + + assertEquals(1, copy.getEnchantmentNames().size()); + assertEquals("bane_of_arthropods", copy.getEnchantmentNames().stream().findFirst().orElse(null)); + + assertEquals(1, copy.getEnchantmentKeys().size()); + assertEquals(NamespacedKey.fromString("bane_of_arthropods"), copy.getEnchantmentKeys().stream().findFirst().orElse(null)); + + assertEquals(2, copy.getExcludedGroupNames().size()); + } + +} diff --git a/src/test/java/xyz/alexcrea/cuanvil/api/CustomAnvilRecipeApiTests.java b/src/test/java/xyz/alexcrea/cuanvil/api/CustomAnvilRecipeApiTests.java new file mode 100644 index 0000000..651b873 --- /dev/null +++ b/src/test/java/xyz/alexcrea/cuanvil/api/CustomAnvilRecipeApiTests.java @@ -0,0 +1,259 @@ +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; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; +import org.junit.jupiter.api.BeforeEach; +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 static org.junit.jupiter.api.Assertions.*; + +public class CustomAnvilRecipeApiTests extends ConfigResetCustomAnvilTest { + + private AnvilInventory anvil; + private PlayerMock player; + + @Override + @BeforeEach + public void setUp() { + super.setUp(); + // Mock used player & open anvil + player = server.addPlayer(); + + Inventory anvil = server.createInventory(player, InventoryType.ANVIL); + + this.anvil = (AnvilInventory) anvil; + player.openInventory(anvil); + + ConfigHolder.DEFAULT_CONFIG.getConfig().set("debug_log", true); + ConfigHolder.DEFAULT_CONFIG.getConfig().set("debug_log_verbose", true); + } + + @Test + public void testBasicRecipe() { + String recipeName = "stick_recipe"; + ItemStack stick = new ItemStackMock(Material.STICK); + + AnvilFuseTestData nullResultData = new AnvilFuseTestData( + stick, stick, + null + ); + + AnvilFuseTestData legalResultData = new AnvilFuseTestData( + stick, stick, + null, stick, null, + 2, + null, null + ); + + // Testing default conflict (no recipe exist) + nullResultData.executeTest(anvil, player); + + // Add and test recipe + AnvilRecipeBuilder builder = new AnvilRecipeBuilder(recipeName); + builder.setExactCount(true).setLeftItem(stick).setResultItem(stick).setLevelCostPerCraft(2); + + assertTrue(builder.registerIfAbsent()); + legalResultData.executeTest(anvil, player); + + AnvilCustomRecipe recipe = getByName(recipeName); + assertNotNull(recipe); + + // Remove recipe + assertTrue(CustomAnvilRecipeApi.removeRecipe(recipe)); + assertFalse(CustomAnvilRecipeApi.removeRecipe(recipe)); + 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)); + nullResultData.executeTest(anvil, player); + + recipe = getByName(recipeName); + assertNull(recipe); + + // Try to add deleted recipe with override (should add) + assertTrue(CustomAnvilRecipeApi.addRecipe(builder, true)); + legalResultData.executeTest(anvil, player); + + recipe = getByName(recipeName); + assertNotNull(recipe); + } + + @Test + public void testUnitRecipe() { + 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 + ); + + AnvilFuseTestData legalResultData1 = new AnvilFuseTestData( + stick, stick, + null, stick2, null, + 2, + null, null + ); + + AnvilFuseTestData legalResultData2 = new AnvilFuseTestData( + stick5, stick, + null, stick10, null, + 10, // 2 * 5 + null, null + ); + + nullResultData.executeTest(anvil, player); + + AnvilRecipeBuilder builder = new AnvilRecipeBuilder(recipeName); + builder.setExactCount(false) + .setLeftItem(stick) + .setResultItem(stick2) + .setLevelCostPerCraft(2); + + assertTrue(builder.registerIfAbsent()); + + // Now working test + 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) { + for (AnvilCustomRecipe registeredRecipe : CustomAnvilRecipeApi.getRegisteredRecipes()) { + if (registeredRecipe.getName().contentEquals(name)) { + return registeredRecipe; + } + } + + return null; + } + +} diff --git a/src/test/java/xyz/alexcrea/cuanvil/api/MaterialGroupApiTests.java b/src/test/java/xyz/alexcrea/cuanvil/api/MaterialGroupApiTests.java new file mode 100644 index 0000000..3fcafe7 --- /dev/null +++ b/src/test/java/xyz/alexcrea/cuanvil/api/MaterialGroupApiTests.java @@ -0,0 +1,108 @@ +package xyz.alexcrea.cuanvil.api; + +import org.bukkit.Material; +import org.junit.jupiter.api.Test; +import xyz.alexcrea.cuanvil.group.EnchantConflictGroup; +import xyz.alexcrea.cuanvil.group.IncludeGroup; +import xyz.alexcrea.cuanvil.tests.ConfigResetCustomAnvilTest; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class MaterialGroupApiTests extends ConfigResetCustomAnvilTest { + + @Test + void groupAddAndRemove() { + String groupName = "group"; + IncludeGroup group = new IncludeGroup(groupName); + group.addToPolicy(Material.DIAMOND_PICKAXE.getKey()); // We do not want it to be empty + + // Group not being set should not exist + assertFalse(doGroupExist(groupName)); + assertFalse(doGroupCanBeFound(groupName)); + + // Add group + assertTrue(MaterialGroupApi.addMaterialGroup(group)); + assertFalse(MaterialGroupApi.addMaterialGroup(group, true)); + + assertTrue(doGroupExist(groupName)); + assertTrue(doGroupCanBeFound(groupName)); + + // Remove group + assertTrue(MaterialGroupApi.removeGroup(group)); + assertFalse(MaterialGroupApi.removeGroup(group)); + + assertFalse(doGroupExist(groupName)); + assertFalse(doGroupCanBeFound(groupName)); + + // Re add + assertFalse(MaterialGroupApi.addMaterialGroup(group, false)); + assertTrue(MaterialGroupApi.addMaterialGroup(group, true)); + + assertTrue(doGroupExist(groupName)); + assertTrue(doGroupCanBeFound(groupName)); + + } + + @Test + void writeGroup_Reload() { + String groupName = "group"; + IncludeGroup group = new IncludeGroup(groupName); + group.addToPolicy(Material.DIAMOND_PICKAXE.getKey()); // We do not want it to be empty + + // Group not being set should not exist + assertFalse(doGroupExist(groupName)); + assertFalse(doGroupCanBeFound(groupName)); + + // Add group and reload + assertTrue(MaterialGroupApi.writeMaterialGroup(group)); + assertFalse(doGroupExist(groupName)); + assertFalse(doGroupCanBeFound(groupName)); + + // Tick so write get reloaded + server.getScheduler().performOneTick(); + + assertTrue(doGroupExist(groupName)); + assertTrue(doGroupCanBeFound(groupName)); + } + + @Test + void writeGroup_Empty() { + String groupName = "group"; + IncludeGroup group = new IncludeGroup(groupName); + + // Add group and reload + assertFalse(MaterialGroupApi.writeMaterialGroup(group)); + assertFalse(doGroupExist(groupName)); + assertFalse(doGroupCanBeFound(groupName)); + + // Tick so write get reloaded + server.getScheduler().performOneTick(); + + assertFalse(doGroupExist(groupName)); + assertFalse(doGroupCanBeFound(groupName)); + } + + @Test + void writeGroup_InvalidDot() { + String groupName = "group.group"; + IncludeGroup group = new IncludeGroup(groupName); + + // Try write group + assertFalse(MaterialGroupApi.writeMaterialGroup(group)); + } + + boolean doGroupExist(String groupName) { + return MaterialGroupApi.getGroup(groupName) != null; + } + + boolean doGroupCanBeFound(String groupName) { + ConflictBuilder builder = new ConflictBuilder(groupName); + builder.addExcludedGroup(groupName); + + EnchantConflictGroup group = builder.build(); + IncludeGroup materialGroup = (IncludeGroup) group.getCantConflictGroup(); + return materialGroup.getGroups().size() == 1; + } + +} diff --git a/src/test/java/xyz/alexcrea/cuanvil/api/UnitRepairApiTests.java b/src/test/java/xyz/alexcrea/cuanvil/api/UnitRepairApiTests.java new file mode 100644 index 0000000..adda4e1 --- /dev/null +++ b/src/test/java/xyz/alexcrea/cuanvil/api/UnitRepairApiTests.java @@ -0,0 +1,118 @@ +package xyz.alexcrea.cuanvil.api; + +import org.bukkit.Material; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.AnvilInventory; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.Repairable; +import org.junit.jupiter.api.BeforeEach; +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.tests.ConfigResetCustomAnvilTest; +import xyz.alexcrea.cuanvil.data.AnvilFuseTestData; +import xyz.alexcrea.cuanvil.util.AnvilFuseTestUtil; + +import static org.junit.jupiter.api.Assertions.*; + +public class UnitRepairApiTests extends ConfigResetCustomAnvilTest { + + private AnvilInventory anvil; + private PlayerMock player; + + @Override + @BeforeEach + public void setUp() { + super.setUp(); + // Mock used player & open anvil + player = server.addPlayer(); + + Inventory anvil = server.createInventory(player, InventoryType.ANVIL); + + this.anvil = (AnvilInventory) anvil; + player.openInventory(anvil); + + ConfigHolder.DEFAULT_CONFIG.getConfig().set("debug_log", true); + ConfigHolder.DEFAULT_CONFIG.getConfig().set("debug_log_verbose", true); + } + + @Test + void vanillaUnitRepair(){ + ItemStack damagedPickaxe = new ItemStackMock(Material.DIAMOND_PICKAXE); + damagedPickaxe.setDurability((short) (Material.DIAMOND_PICKAXE.getMaxDurability() -1)); + + ItemStack resultPickaxe = new ItemStackMock(Material.DIAMOND_PICKAXE); + resultPickaxe.setDurability((short) (Material.DIAMOND_PICKAXE.getMaxDurability()/2)); + ItemMeta meta = resultPickaxe.getItemMeta(); + ((Repairable) meta).setRepairCost(1); + resultPickaxe.setItemMeta(meta); + + ItemStack diamond2 = new ItemStackMock(Material.DIAMOND, 2); + + AnvilFuseTestData legalResultData = new AnvilFuseTestData( + damagedPickaxe, diamond2, + resultPickaxe, + 2 + ); + + legalResultData.executeTest(anvil, player); + } + + @Test + void removeUnitRepair(){ + ItemStack damagedPickaxe = new ItemStackMock(Material.DIAMOND_PICKAXE); + damagedPickaxe.setDurability((short) (Material.DIAMOND_PICKAXE.getMaxDurability() -1)); + + ItemStack diamond2 = new ItemStackMock(Material.DIAMOND, 2); + + AnvilFuseTestData nullResultData = new AnvilFuseTestData( + damagedPickaxe, diamond2, + null + ); + + // Remove unit repair + assertTrue(UnitRepairApi.removeUnitRepair(Material.DIAMOND, Material.DIAMOND_PICKAXE)); + + nullResultData.executeTest(anvil, player); + + // see override + assertFalse(UnitRepairApi.addUnitRepair(Material.DIAMOND, Material.DIAMOND_PICKAXE, 0.25)); + assertTrue(UnitRepairApi.addUnitRepair(Material.DIAMOND, Material.DIAMOND_PICKAXE, 0.25, true)); + } + + + @Test + void addUnitRepair(){ + ItemStack damagedPickaxe = new ItemStackMock(Material.DIAMOND_PICKAXE); + damagedPickaxe.setDurability((short) (Material.DIAMOND_PICKAXE.getMaxDurability() -1)); + + ItemStack resultPickaxe = new ItemStackMock(Material.DIAMOND_PICKAXE); + resultPickaxe.setDurability((short) (Material.DIAMOND_PICKAXE.getMaxDurability()/2)); + ItemMeta meta = resultPickaxe.getItemMeta(); + ((Repairable) meta).setRepairCost(1); + resultPickaxe.setItemMeta(meta); + + ItemStack stick2 = new ItemStackMock(Material.STICK, 2); + + AnvilFuseTestData nullResultData = new AnvilFuseTestData( + damagedPickaxe, stick2, + null + ); + AnvilFuseTestData legalResultData = new AnvilFuseTestData( + damagedPickaxe, stick2, + resultPickaxe, + 2 + ); + + nullResultData.executeTest(anvil, player); + + // Add unit repair + assertTrue(UnitRepairApi.addUnitRepair(Material.STICK, Material.DIAMOND_PICKAXE)); + assertFalse(UnitRepairApi.addUnitRepair(Material.STICK, Material.DIAMOND_PICKAXE)); + 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/config/DefaultConfigTests.java b/src/test/java/xyz/alexcrea/cuanvil/config/DefaultConfigTests.java new file mode 100644 index 0000000..ac3f73e --- /dev/null +++ b/src/test/java/xyz/alexcrea/cuanvil/config/DefaultConfigTests.java @@ -0,0 +1,89 @@ +package xyz.alexcrea.cuanvil.config; + +import io.delilaheve.util.ConfigOptions; +import io.papermc.paper.registry.RegistryAccess; +import io.papermc.paper.registry.RegistryKey; +import org.bukkit.NamespacedKey; +import org.bukkit.configuration.file.FileConfiguration; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import xyz.alexcrea.cuanvil.enchant.CAEnchantment; +import xyz.alexcrea.cuanvil.enchant.CAEnchantmentRegistry; +import xyz.alexcrea.cuanvil.enchant.EnchantmentRarity; +import xyz.alexcrea.cuanvil.tests.SharedCustomAnvilTest; + +import java.util.stream.Stream; + +public class DefaultConfigTests extends SharedCustomAnvilTest { + + @ParameterizedTest + @MethodSource("provideStringsForIsConfiguredValueValid") + public void isConfiguredValueValid(String path, Object value){ + FileConfiguration config = ConfigHolder.DEFAULT_CONFIG.getConfig(); + + Assertions.assertEquals(value.toString(), config.getString(path), "Default value is not the same as in the config file"); + } + + @ParameterizedTest + @MethodSource("provideForEnchantmentsTests") + public void testDefaultEnchantLimit(NamespacedKey key){ + CAEnchantment enchantment = CAEnchantmentRegistry.getInstance().getByKey(key); + Assertions.assertNotNull(enchantment, "Enchantment was somehow not found"); + + int levelLimit = ConfigOptions.INSTANCE.enchantLimit(enchantment); + int mergeLimit = ConfigOptions.INSTANCE.maxBeforeMergeDisabled(enchantment); + + Assertions.assertEquals(enchantment.defaultMaxLevel(), levelLimit,"Default enchantment limit is not the same as expected limit "); + + if(mergeLimit >= 0) { // negative mean default + Assertions.assertEquals(enchantment.defaultMaxLevel(), mergeLimit,"Default enchantment merge limit is not the same as expected limit "); + } + + } + + @ParameterizedTest + @MethodSource("provideForEnchantmentsTests") + public void testDefaultEnchantValues(NamespacedKey key){ + CAEnchantment enchantment = CAEnchantmentRegistry.getInstance().getByKey(key); + Assertions.assertNotNull(enchantment, "Enchantment was somehow not found"); + + int itemValue = ConfigOptions.INSTANCE.enchantmentValue(enchantment, false); + int bookValue = ConfigOptions.INSTANCE.enchantmentValue(enchantment, true); + + EnchantmentRarity rarity = enchantment.defaultRarity(); + + Assertions.assertEquals(rarity.getItemValue(), itemValue,"Default enchantment item value is not the same as expected value"); + Assertions.assertEquals(rarity.getBookValue(), bookValue,"Default enchantment book value is not the same as expected value"); + } + + + private static Stream provideStringsForIsConfiguredValueValid() { + return Stream.of( + // Default options + Arguments.of(ConfigOptions.CAP_ANVIL_COST, ConfigOptions.DEFAULT_CAP_ANVIL_COST), + Arguments.of(ConfigOptions.CAP_ANVIL_COST, ConfigOptions.DEFAULT_CAP_ANVIL_COST), + Arguments.of(ConfigOptions.MAX_ANVIL_COST, ConfigOptions.DEFAULT_MAX_ANVIL_COST), + Arguments.of(ConfigOptions.REMOVE_ANVIL_COST_LIMIT, ConfigOptions.DEFAULT_REMOVE_ANVIL_COST_LIMIT), + Arguments.of(ConfigOptions.REPLACE_TOO_EXPENSIVE, ConfigOptions.DEFAULT_REPLACE_TOO_EXPENSIVE), + Arguments.of(ConfigOptions.ITEM_REPAIR_COST, ConfigOptions.DEFAULT_ITEM_REPAIR_COST), + Arguments.of(ConfigOptions.UNIT_REPAIR_COST, ConfigOptions.DEFAULT_UNIT_REPAIR_COST), + Arguments.of(ConfigOptions.ITEM_RENAME_COST, ConfigOptions.DEFAULT_ITEM_RENAME_COST), + Arguments.of(ConfigOptions.SACRIFICE_ILLEGAL_COST, ConfigOptions.DEFAULT_SACRIFICE_ILLEGAL_COST), + // Color options + Arguments.of(ConfigOptions.ALLOW_COLOR_CODE, ConfigOptions.DEFAULT_ALLOW_COLOR_CODE), + Arguments.of(ConfigOptions.ALLOW_HEXADECIMAL_COLOR, ConfigOptions.DEFAULT_ALLOW_HEXADECIMAL_COLOR), + Arguments.of(ConfigOptions.PERMISSION_NEEDED_FOR_COLOR, ConfigOptions.DEFAULT_PERMISSION_NEEDED_FOR_COLOR), + Arguments.of(ConfigOptions.USE_OF_COLOR_COST, ConfigOptions.DEFAULT_USE_OF_COLOR_COST) + + ); + } + + // Test every enchantment defaults + private static Stream provideForEnchantmentsTests() { + return RegistryAccess.registryAccess().getRegistry(RegistryKey.ENCHANTMENT) + .stream().map(enchantment -> Arguments.of(enchantment.getKey())); + } + +} diff --git a/src/test/java/xyz/alexcrea/cuanvil/data/AnvilClickTestData.java b/src/test/java/xyz/alexcrea/cuanvil/data/AnvilClickTestData.java new file mode 100644 index 0000000..002b194 --- /dev/null +++ b/src/test/java/xyz/alexcrea/cuanvil/data/AnvilClickTestData.java @@ -0,0 +1,57 @@ +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, + @Nullable ItemStack rightItem, + @Nullable ItemStack resultSlotItem, + @Nullable ItemStack expectedCursor, + int levelCost, + @Nullable Event.Result expectedResult, + boolean testNoLevelNoChange, + @Nullable Event.Result npChangeResult +) { + + public AnvilClickTestData(@Nullable ItemStack leftItem, + @Nullable ItemStack rightItem, + @Nullable ItemStack resultSlotItem, + @Nullable ItemStack expectedCursor, + int levelCost, + @Nullable Event.Result expectedResult) { + this(leftItem, rightItem, resultSlotItem, + expectedCursor, levelCost, expectedResult, + false, null); + } + + public AnvilClickTestData(@Nullable ItemStack leftItem, + @Nullable ItemStack rightItem, + @Nullable ItemStack resultSlotItem, + @Nullable ItemStack expectedCursor, + int levelCost) { + this(leftItem, rightItem, resultSlotItem, + expectedCursor, levelCost, null); + } + + public AnvilClickTestData(@Nullable ItemStack expectedCursor, + int levelCost, + @Nullable Event.Result expectedResult) { + this(null, null, null, + expectedCursor, levelCost, expectedResult, + false, null); + } + + public AnvilClickTestData(@Nullable ItemStack expectedCursor, + 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 new file mode 100644 index 0000000..8542d41 --- /dev/null +++ b/src/test/java/xyz/alexcrea/cuanvil/data/AnvilFuseTestData.java @@ -0,0 +1,61 @@ +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, + @Nullable ItemStack rightItem, + @Nullable ItemStack expectedResult, + + @Nullable ItemStack expectedAfterLeftPlaced, + @Nullable ItemStack expectedAfterRightPlaced, + + @Nullable Integer expectedPriceAfterLeftPlaced, + @Nullable Integer expectedPriceAfterRightPlaced, + @Nullable Integer expectedPriceAfterBothPlaced + ){ + + public AnvilFuseTestData( + @Nullable ItemStack leftItem, + @Nullable ItemStack rightItem, + @Nullable ItemStack expectedResult, + @Nullable Integer expectedPriceAfterBothPlaced + ){ + this(leftItem, rightItem, expectedResult, + null, null, null, null, + expectedPriceAfterBothPlaced + ); + } + + public AnvilFuseTestData( + @Nullable ItemStack leftItem, + @Nullable ItemStack rightItem, + @Nullable ItemStack expectedResult + ){ + this(leftItem, rightItem, expectedResult, null + ); + } + + public AnvilFuseTestData( + @Nullable ItemStack leftItem, + @Nullable ItemStack rightItem, + @Nullable ItemStack expectedResult, + + @Nullable ItemStack expectedAfterLeftPlaced, + @Nullable ItemStack expectedAfterRightPlaced + ){ + this(leftItem, rightItem, + expectedResult, expectedAfterLeftPlaced, expectedAfterRightPlaced, + null, null, null + ); + } + + 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 new file mode 100644 index 0000000..b4f47a5 --- /dev/null +++ b/src/test/java/xyz/alexcrea/cuanvil/data/TestDataContainer.java @@ -0,0 +1,250 @@ +package xyz.alexcrea.cuanvil.data; + +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.AnvilInventory; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.junit.jupiter.api.Assertions; +import xyz.alexcrea.cuanvil.util.AnvilFuseTestUtil; + +@SuppressWarnings("unused") +public record TestDataContainer( + @NotNull AnvilFuseTestData fuseData, + @Nullable AnvilClickTestData clickData +) { + + public void executeTest(AnvilInventory anvil, Player player) { + fuseData.executeTest(anvil, player); + if (clickData != null) clickData.executeTest(anvil, player); + } + + public void executeFuseTest(AnvilInventory anvil, HumanEntity player) { + fuseData.executeTest(anvil, player); + } + + public void executeClickTest(AnvilInventory anvil, Player player) { + Assertions.assertNotNull(clickData); + clickData.executeTest(anvil, player); + } + + public @NotNull TestDataContainer nullifyResult() { + return new TestDataContainer( + new AnvilFuseTestData( + fuseData.leftItem(), fuseData.rightItem(), + null + ), null); + } + + public @NotNull TestDataContainer setCost( + @Nullable Integer priceAfterLeft, + @Nullable Integer priceAfterRight, + int priceAfterBoth + ) { + AnvilFuseTestData data = new AnvilFuseTestData( + fuseData.leftItem(), fuseData.rightItem(), fuseData.expectedResult(), + fuseData.expectedAfterLeftPlaced(), + fuseData.expectedAfterRightPlaced(), + priceAfterLeft, + priceAfterRight, + priceAfterBoth + ); + + AnvilClickTestData CData; + if (clickData == null) { + CData = null; + } else { + CData = new AnvilClickTestData( + clickData.leftItem(), clickData.rightItem(), clickData.resultSlotItem(), + clickData.expectedCursor(), priceAfterBoth, + clickData.expectedResult(), + clickData.testNoLevelNoChange(), clickData.npChangeResult() + ); + } + return new TestDataContainer(data, CData); + } + + public @NotNull TestDataContainer setCost( + int priceAfterBoth + ) { + return setCost(null, null, priceAfterBoth); + } + + // Set fuse items + public @NotNull TestDataContainer setFuseItems(@Nullable ItemStack left, @Nullable ItemStack right, @Nullable ItemStack expected) { + AnvilFuseTestData data = new AnvilFuseTestData( + left, right, expected, + fuseData.expectedAfterLeftPlaced(), + fuseData.expectedAfterRightPlaced(), + fuseData.expectedPriceAfterLeftPlaced(), + fuseData.expectedPriceAfterRightPlaced(), + fuseData.expectedPriceAfterBothPlaced() + ); + return new TestDataContainer(data, clickData); + } + + public @NotNull TestDataContainer setFuseItems( + @Nullable ItemStack left, @Nullable ItemStack right, @Nullable ItemStack expected, + @Nullable ItemStack leftExpected, @Nullable ItemStack rightExpected) { + AnvilFuseTestData data = new AnvilFuseTestData( + left, right, expected, + leftExpected, + rightExpected, + fuseData.expectedPriceAfterLeftPlaced(), + fuseData.expectedPriceAfterRightPlaced(), + fuseData.expectedPriceAfterBothPlaced() + ); + return new TestDataContainer(data, clickData); + } + + public @NotNull TestDataContainer setFuseLeft(@Nullable ItemStack left) { + AnvilFuseTestData data = new AnvilFuseTestData( + left, fuseData.rightItem(), fuseData.expectedResult(), + fuseData.expectedAfterLeftPlaced(), + fuseData.expectedAfterRightPlaced(), + fuseData.expectedPriceAfterLeftPlaced(), + fuseData.expectedPriceAfterRightPlaced(), + fuseData.expectedPriceAfterBothPlaced() + ); + return new TestDataContainer(data, clickData); + } + + public @NotNull TestDataContainer setFuseRight(@Nullable ItemStack right) { + AnvilFuseTestData data = new AnvilFuseTestData( + fuseData.leftItem(), right, fuseData.expectedResult(), + fuseData.expectedAfterLeftPlaced(), + fuseData.expectedAfterRightPlaced(), + fuseData.expectedPriceAfterLeftPlaced(), + fuseData.expectedPriceAfterRightPlaced(), + fuseData.expectedPriceAfterBothPlaced() + ); + return new TestDataContainer(data, clickData); + } + + public @NotNull TestDataContainer setFuseExpected(@Nullable ItemStack expected) { + AnvilFuseTestData data = new AnvilFuseTestData( + fuseData.leftItem(), fuseData.rightItem(), expected, + fuseData.expectedAfterLeftPlaced(), + fuseData.expectedAfterRightPlaced(), + fuseData.expectedPriceAfterLeftPlaced(), + fuseData.expectedPriceAfterRightPlaced(), + fuseData.expectedPriceAfterBothPlaced() + ); + return new TestDataContainer(data, clickData); + } + + public @NotNull TestDataContainer setFuseExpectedLeft(@Nullable ItemStack expected) { + AnvilFuseTestData data = new AnvilFuseTestData( + fuseData.leftItem(), fuseData.rightItem(), fuseData.expectedResult(), + expected, + fuseData.expectedAfterRightPlaced(), + fuseData.expectedPriceAfterLeftPlaced(), + fuseData.expectedPriceAfterRightPlaced(), + fuseData.expectedPriceAfterBothPlaced() + ); + return new TestDataContainer(data, clickData); + } + + public @NotNull TestDataContainer setFuseExpectedRight(@Nullable ItemStack expected) { + AnvilFuseTestData data = new AnvilFuseTestData( + fuseData.leftItem(), fuseData.rightItem(), fuseData.expectedResult(), + fuseData.expectedAfterLeftPlaced(), + expected, + fuseData.expectedPriceAfterLeftPlaced(), + fuseData.expectedPriceAfterRightPlaced(), + fuseData.expectedPriceAfterBothPlaced() + ); + return new TestDataContainer(data, clickData); + } + + // Set click items + public @NotNull TestDataContainer setClickLeft(@Nullable ItemStack left) { + if (clickData == null) return this; + AnvilClickTestData data = new AnvilClickTestData( + left, clickData.rightItem(), clickData.resultSlotItem(), clickData.expectedCursor(), + clickData.levelCost(), clickData.expectedResult(), + clickData.testNoLevelNoChange(), clickData.npChangeResult() + ); + + return new TestDataContainer(fuseData, data); + } + + public @NotNull TestDataContainer setClickRight(@Nullable ItemStack right) { + if (clickData == null) return this; + AnvilClickTestData data = new AnvilClickTestData( + clickData.leftItem(), right, clickData.resultSlotItem(), clickData.expectedCursor(), + clickData.levelCost(), clickData.expectedResult(), + clickData.testNoLevelNoChange(), clickData.npChangeResult() + ); + + return new TestDataContainer(fuseData, data); + } + + public @NotNull TestDataContainer setClickOutput(@Nullable ItemStack output) { + if (clickData == null) return this; + AnvilClickTestData data = new AnvilClickTestData( + clickData.leftItem(), clickData.rightItem(), output, clickData.expectedCursor(), + clickData.levelCost(), clickData.expectedResult(), + clickData.testNoLevelNoChange(), clickData.npChangeResult() + ); + + return new TestDataContainer(fuseData, data); + } + + public @NotNull TestDataContainer setClickCursor(@Nullable ItemStack cursor) { + if (clickData == null) return this; + AnvilClickTestData data = new AnvilClickTestData( + clickData.leftItem(), clickData.rightItem(), clickData.resultSlotItem(), cursor, + clickData.levelCost(), clickData.expectedResult(), + clickData.testNoLevelNoChange(), clickData.npChangeResult() + ); + + return new TestDataContainer(fuseData, data); + } + + // Both item + public @NotNull TestDataContainer setExpectedResult(@Nullable ItemStack result) { + return setFuseExpected(result).setClickCursor(result); + } + + // Get fuse item + public @Nullable ItemStack getLeftFuse() { + return fuseData.leftItem(); + } + + public @Nullable ItemStack getRightFuse() { + return fuseData.rightItem(); + } + + public @Nullable ItemStack getExpectedFuse() { + return fuseData.expectedResult(); + } + + public @Nullable ItemStack getLeftExpectedFuse() { + return fuseData.expectedAfterLeftPlaced(); + } + + public @Nullable ItemStack getRightExpectedFuse() { + return fuseData.expectedAfterRightPlaced(); + } + + // Get click item + public @Nullable ItemStack getLeftClick() { + return clickData == null ? null : clickData.leftItem(); + } + + public @Nullable ItemStack getRightClick() { + return clickData == null ? null : clickData.rightItem(); + } + + public @Nullable ItemStack getOutputClick() { + return clickData == null ? null : clickData.resultSlotItem(); + } + + public @Nullable ItemStack getCursorClick() { + return clickData == null ? null : clickData.expectedCursor(); + } + + +} diff --git a/src/test/java/xyz/alexcrea/cuanvil/mock/AnvilViewMock.java b/src/test/java/xyz/alexcrea/cuanvil/mock/AnvilViewMock.java new file mode 100644 index 0000000..afcbb26 --- /dev/null +++ b/src/test/java/xyz/alexcrea/cuanvil/mock/AnvilViewMock.java @@ -0,0 +1,81 @@ +package xyz.alexcrea.cuanvil.mock; + +import org.bukkit.entity.HumanEntity; +import org.bukkit.inventory.AnvilInventory; +import org.bukkit.inventory.view.AnvilView; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.mockbukkit.mockbukkit.inventory.PlayerInventoryViewMock; + +@SuppressWarnings({"removal"}) +public class AnvilViewMock extends PlayerInventoryViewMock implements AnvilView { + + private @NotNull AnvilInventory top; + + /** + * Constructs a new {@link PlayerInventoryViewMock} for the provided player, with the specified top inventory. + * + * @param player The player to create the view for. + * @param top The top inventory. + */ + public AnvilViewMock(@NotNull HumanEntity player, @NotNull AnvilInventory top) { + super(player, top); + this.top = top; + } + + @Override + public @Nullable String getRenameText() { + return top.getRenameText(); + } + + @Override + public int getRepairItemCountCost() { + return top.getRepairCostAmount(); + } + + @Override + public int getRepairCost() { + return top.getRepairCost(); + } + + @Override + public int getMaximumRepairCost() { + return top.getMaximumRepairCost(); + } + + @Override + public void setRepairItemCountCost(int amount) { + top.setRepairCostAmount(amount); + } + + @Override + public void setRepairCost(int cost) { + top.setRepairCost(cost); + } + + @Override + public void setMaximumRepairCost(int levels) { + top.setMaximumRepairCost(levels); + } + + @Override + public boolean bypassesEnchantmentLevelRestriction() { + throw new UnsupportedOperationException("This is currently is not used in CustomAnvil"); + } + + @Override + public void bypassEnchantmentLevelRestriction(boolean bypassEnchantmentLevelRestriction) { + throw new UnsupportedOperationException("This is currently is not used in CustomAnvil"); + } + + @Override + public @NotNull AnvilInventory getTopInventory() { + 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/ConfigResetCustomAnvilTest.java b/src/test/java/xyz/alexcrea/cuanvil/tests/ConfigResetCustomAnvilTest.java new file mode 100644 index 0000000..3c03bb4 --- /dev/null +++ b/src/test/java/xyz/alexcrea/cuanvil/tests/ConfigResetCustomAnvilTest.java @@ -0,0 +1,38 @@ +package xyz.alexcrea.cuanvil.tests; + +import org.junit.jupiter.api.AfterEach; +import xyz.alexcrea.cuanvil.config.ConfigHolder; + +import java.io.File; + +public abstract class ConfigResetCustomAnvilTest extends DefaultCustomAnvilTest { + + @Override + @AfterEach + public void tearDown() { + // Destroy saved config file + String[] configs = new String[]{ + "config.yml", + "item_groups.yml", + "enchant_conflict.yml", + "unit_repair_item.yml", + "custom_recipes.yml" + }; + for (String config : configs) { + File configFile = new File(plugin.getDataFolder(), config); + configFile.delete(); + } + + // Set config to null + ConfigHolder.DEFAULT_CONFIG = null; + ConfigHolder.ITEM_GROUP_HOLDER = null; + ConfigHolder.CONFLICT_HOLDER = null; + ConfigHolder.UNIT_REPAIR_HOLDER = null; + ConfigHolder.CUSTOM_RECIPE_HOLDER = null; + + // Do parent works + super.tearDown(); + } + + +} diff --git a/src/test/java/xyz/alexcrea/cuanvil/tests/DefaultCustomAnvilTest.java b/src/test/java/xyz/alexcrea/cuanvil/tests/DefaultCustomAnvilTest.java new file mode 100644 index 0000000..bdb280c --- /dev/null +++ b/src/test/java/xyz/alexcrea/cuanvil/tests/DefaultCustomAnvilTest.java @@ -0,0 +1,54 @@ +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; +import xyz.alexcrea.cuanvil.enchant.CAEnchantment; +import xyz.alexcrea.cuanvil.enchant.CAEnchantmentRegistry; + +import java.util.ArrayList; +import java.util.List; + +public abstract class DefaultCustomAnvilTest { + + protected static ServerMock server; + protected CustomAnvil plugin; + + @BeforeAll + public static void setupMock() { + server = MockBukkit.mock(); + } + + @BeforeEach + public void setUp() { + // Load your plugin + plugin = MockBukkit.load(CustomAnvil.class); + // Continue initialization of the plugin + server.getScheduler().performOneTick(); + } + + @AfterEach + public void tearDown() { + // Unregister enchantments + List toUnregister = new ArrayList<>( + CAEnchantmentRegistry.getInstance().values() + ); + + for (CAEnchantment caEnchantment : toUnregister) { + 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/tests/SharedCustomAnvilTest.java b/src/test/java/xyz/alexcrea/cuanvil/tests/SharedCustomAnvilTest.java new file mode 100644 index 0000000..02844f4 --- /dev/null +++ b/src/test/java/xyz/alexcrea/cuanvil/tests/SharedCustomAnvilTest.java @@ -0,0 +1,45 @@ +package xyz.alexcrea.cuanvil.tests; + +import io.delilaheve.CustomAnvil; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.mockbukkit.mockbukkit.MockBukkit; +import org.mockbukkit.mockbukkit.ServerMock; +import xyz.alexcrea.cuanvil.enchant.CAEnchantment; +import xyz.alexcrea.cuanvil.enchant.CAEnchantmentRegistry; + +import java.util.ArrayList; +import java.util.List; + +public abstract class SharedCustomAnvilTest { + + protected static ServerMock server; + protected static CustomAnvil plugin; + + @BeforeAll + public static void setUp() { + // Start the mock server + server = MockBukkit.mock(); + // Load your plugin + plugin = MockBukkit.load(CustomAnvil.class); + // Continue initialization of the plugin + server.getScheduler().performOneTick(); + } + + @AfterAll + public static void tearDown() { + // Stop the mock server + MockBukkit.unmock(); + + // Unregister enchantments + List toUnregister = new ArrayList<>( + CAEnchantmentRegistry.getInstance().values() + ); + + for (CAEnchantment caEnchantment : toUnregister) { + CAEnchantmentRegistry.getInstance().unregister(caEnchantment); + } + + } + +} diff --git a/src/test/java/xyz/alexcrea/cuanvil/tests/SharedOnlyMockBukkit.java b/src/test/java/xyz/alexcrea/cuanvil/tests/SharedOnlyMockBukkit.java new file mode 100644 index 0000000..0f38239 --- /dev/null +++ b/src/test/java/xyz/alexcrea/cuanvil/tests/SharedOnlyMockBukkit.java @@ -0,0 +1,24 @@ +package xyz.alexcrea.cuanvil.tests; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.mockbukkit.mockbukkit.MockBukkit; +import org.mockbukkit.mockbukkit.ServerMock; + +public class SharedOnlyMockBukkit { + + protected static ServerMock server; + + @BeforeAll + public static void setUp() { + // Start the mock server + server = MockBukkit.mock(); + } + + @AfterAll + public static void tearDown() { + // 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 new file mode 100644 index 0000000..6f5c7bb --- /dev/null +++ b/src/test/java/xyz/alexcrea/cuanvil/util/AnvilFuseTestUtil.java @@ -0,0 +1,271 @@ +package xyz.alexcrea.cuanvil.util; + +import io.delilaheve.util.ItemUtil; +import org.bukkit.Material; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.inventory.*; +import org.bukkit.inventory.AnvilInventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.Repairable; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.junit.jupiter.api.Assertions; +import xyz.alexcrea.cuanvil.data.AnvilClickTestData; +import xyz.alexcrea.cuanvil.data.AnvilFuseTestData; +import xyz.alexcrea.cuanvil.enchant.CAEnchantment; +import xyz.alexcrea.cuanvil.listener.AnvilResultListener; +import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener; +import xyz.alexcrea.cuanvil.mock.AnvilViewMock; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class AnvilFuseTestUtil { + + private static PrepareAnvilListener PREPARE_LISTENER = new PrepareAnvilListener(); + private static AnvilResultListener RESULT_LISTENER = new AnvilResultListener(); + + public static ItemStack prepareItem(@NotNull Material material, + @NotNull List enchantments, + @NotNull List level) { + return prepareItem(material, 0, enchantments, level); + } + + public static ItemStack prepareItem(@NotNull Material material, + int repairCost, + @NotNull List enchantments, + @NotNull List level) { + Assertions.assertEquals(enchantments.size(), level.size()); + + HashMap enchantmentMap = new HashMap<>(); + for (int i = 0; i < enchantments.size(); i++) { + enchantmentMap.put(enchantments.get(i), level.get(i)); + } + + ItemStack item = new ItemStack(material); + ItemUtil.INSTANCE.setEnchantmentsUnsafe(item, enchantmentMap); + + ItemMeta meta = item.getItemMeta(); + ((Repairable) meta).setRepairCost(repairCost); + item.setItemMeta(meta); + + return item; + } + + + public static ItemStack prepareItem(@NotNull Material material, + @NotNull List enchantmentNames, + Integer... levels) { + return prepareItem(material, 0, enchantmentNames, levels); + } + + public static ItemStack prepareItem(@NotNull Material material, + int repairCost, + @NotNull List enchantmentNames, + Integer... levels) { + List enchantments = new ArrayList<>(); + + for (String enchantmentName : enchantmentNames) { + List enchantmentList = CAEnchantment.getListByName(enchantmentName); + Assertions.assertNotEquals(0, enchantmentList.size(), + "Could not find enchantment \"" + enchantmentName + "\""); + + enchantments.addAll(enchantmentList); + } + + return prepareItem(material, repairCost, enchantments, List.of(levels)); + } + + + /* + * Need to use that as it seems setting item in the inventory will not trigger the anvil click even + * + * Not the best for non-custom anvil plugins but work in the context of CA + */ + public static void imitateAnvilUpdate( + @NotNull HumanEntity player, + @NotNull AnvilInventory anvil) { + + AnvilViewMock view = new AnvilViewMock(player, anvil); + try { + PrepareAnvilEvent event = new PrepareAnvilEvent(view, anvil.getItem(2)); + + // Not ideal but possible and the easiest so why not + PREPARE_LISTENER.anvilCombineCheck(event); + anvil.setResult(event.getResult()); + } catch (Exception e) { + Assertions.fail(e); + } + } + + public static void executeAnvilFuseTest( + @NotNull AnvilInventory anvil, + @NotNull HumanEntity player, + @NotNull AnvilFuseTestData data + ) { + 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 + 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 + 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 + 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( + @NotNull AnvilInventory anvil, + @NotNull Player player, + @NotNull AnvilClickTestData data + ) { + if (data.testNoLevelNoChange()) { + ItemStack left = anvil.getFirstItem(); + ItemStack right = anvil.getSecondItem(); + ItemStack result = anvil.getResult(); + + player.setLevel(0); + player.setExp(0); + player.setItemOnCursor(null); + + // Do a test with not enough level + simulateClick(anvil, player, data.npChangeResult()); + + // Nothing should have changed + assertEqual(left, anvil.getFirstItem()); + assertEqual(right, anvil.getSecondItem()); + assertEqual(result, anvil.getResult()); + assertEqual(null, player.getItemOnCursor()); + } + player.setLevel(data.levelCost()); + player.setExp(0); + player.setItemOnCursor(null); + + simulateClick(anvil, player, data.expectedResult()); + + // Should have simulated the click + assertEqual(data.leftItem(), anvil.getFirstItem()); + assertEqual(data.rightItem(), anvil.getSecondItem()); + assertEqual(data.resultSlotItem(), anvil.getResult()); + assertEqual(data.expectedCursor(), data.expectedCursor()); + + // Test if the player has no more xp + Assertions.assertEquals(0, player.getLevel(), "Player has more level than expected"); + } + + private static void simulateClick( + @NotNull AnvilInventory anvil, + @NotNull Player player, + @Nullable Event.Result expectedResult + ) { + AnvilViewMock view = new AnvilViewMock(player, anvil); + try { + InventoryClickEvent event = new InventoryClickEvent(view, + InventoryType.SlotType.RESULT, + PrepareAnvilListener.ANVIL_OUTPUT_SLOT, + ClickType.LEFT, + InventoryAction.PICKUP_ALL); + + RESULT_LISTENER.anvilExtractionCheck(event); + if (expectedResult != null) { + Assertions.assertEquals(expectedResult, event.getResult()); + } + } catch (Exception e) { + Assertions.fail(e); + } + } + + @SuppressWarnings({"removal"}) + private static void testPlacingItem( + @NotNull AnvilInventory anvil, + @NotNull HumanEntity player, + int slot, + Integer expectedPrice, + @Nullable ItemStack toPlace, + @Nullable ItemStack expectedResult) { + anvil.setItem(slot, toPlace); + anvil.setItem(2, null); + AnvilFuseTestUtil.imitateAnvilUpdate(player, anvil); + + ItemStack result = anvil.getItem(2); + assertEqual(expectedResult, result); + + assertPriceEqual(expectedPrice, anvil.getRepairCost()); + } + + 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."); + else { + Assertions.assertFalse(secondIsAir, "Item " + other + " is air but was expected to be " + expected); + + expected.setDurability(expected.getDurability()); + other.setDurability(other.getDurability()); + Assertions.assertEquals(expected, other); + } + + } + + public static boolean isAir(@Nullable ItemStack item) { + return item == null || item.isEmpty() || item.getAmount() == 0; + } + + public static void assertPriceEqual(Integer expectedPrice, int price) { + if (expectedPrice == null) return; + Assertions.assertEquals(expectedPrice, price, "Price of anvil fuse was wrong"); + } + +} diff --git a/src/test/java/xyz/alexcrea/cuanvil/util/CommonItemUtil.java b/src/test/java/xyz/alexcrea/cuanvil/util/CommonItemUtil.java new file mode 100644 index 0000000..1228cda --- /dev/null +++ b/src/test/java/xyz/alexcrea/cuanvil/util/CommonItemUtil.java @@ -0,0 +1,27 @@ +package xyz.alexcrea.cuanvil.util; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +import java.util.List; + +public class CommonItemUtil { + + public static ItemStack sharpness(int level){ + return AnvilFuseTestUtil.prepareItem( + Material.DIAMOND_SWORD, + List.of("sharpness"), + level + ); + } + + public static ItemStack bane_of_arthropods(int level){ + return AnvilFuseTestUtil.prepareItem( + Material.DIAMOND_SWORD, + List.of("bane_of_arthropods"), + level + ); + } + + +} diff --git a/src/test/resources/plugin.yml b/src/test/resources/plugin.yml new file mode 100644 index 0000000..37fb95a --- /dev/null +++ b/src/test/resources/plugin.yml @@ -0,0 +1,73 @@ +main: io.delilaheve.CustomAnvil +name: CustomAnvil +prefix: "Custom Anvil" +version: test +folia-supported: true +description: Allow to customise anvil mechanics +api-version: 1.16 +load: POSTWORLD +authors: [ DelilahEve, alexcrea ] +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 + aliases: + #- acreload # anvil config reload + #- careload # custom anvil reload + - carl # custom anvil reload + customanvilconfig: + description: open a menu for administrator to edit plugin's config in game + permission: ca.config.edit + aliases: + - configanvil + +permissions: + ca.affected: + default: true + description: Player with this permission will be affected by the plugin + ca.bypass.fuse: + default: false + description: Allow player to combine every enchantments to every item (no custom limit) + ca.bypass.level: + default: false + description: Allow player to bypass every level limit (no custom limit) + 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 + # color permissions + ca.color.code: + default: op + description: Allow player to use color code if enabled (toggleable) + ca.color.hex: + default: op + description: Allow player to use hexadecimal color if enabled (toggleable) + # lore edit permissions + ca.lore_edit.book: + default: op + description: Allow player to edit lore via book and quil if enabled (toggleable) + ca.lore_edit.paper: + default: op + description: Allow player to edit lore via paper if enabled (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 +softdepend: + - UnsafeEnchantsPlus + - ProtocolLib + - Disenchantment + - EnchantsSquared + - EcoEnchants + - eco + - ExcellentEnchants + - HavenBags