Compare commits

...

178 commits

Author SHA1 Message Date
8447233b1e
Merge remote-tracking branch 'origin/v1.x.x' into v1.x.x 2026-06-22 03:12:11 +02:00
eb2e7b3abb
allow enchanted book for super enchant 2026-06-22 03:12:04 +02:00
7f7f049b7b
Update COMPATIBILITY.md 2026-06-22 01:01:45 +02:00
37e8ca7da9
update excellentenchants 2026-06-20 23:49:03 +02:00
95d3cf3228
update nightexpress 2026-06-20 23:43:01 +02:00
178b372255
paper mns use getter/setter no property access [ci skip] 2026-06-20 14:57:12 +02:00
106cd53b02
mark 26.2.x as supported on modrinth & hangar [ci skip] 2026-06-20 03:28:07 +02:00
950bad2168
versions bump 2026-06-19 20:31:43 +02:00
bc9cbe0b44
fix and simplify xp handling
- Fix xp limit not being respected
- Fix player xp not being check causing error
2026-06-19 20:29:10 +02:00
9d616d2fd0
remove use of legacy currentMinecraftVersionArray 2026-06-17 02:15:34 +02:00
f82ccfa07e
make Version work with experimental 26.1 paper build 2026-06-17 02:15:04 +02:00
cff94a2c5a
better thanks and put names in compatibility note [ci skip] 2026-06-16 04:05:17 +02:00
4b5133c872
markdown issue² 2026-06-16 03:49:32 +02:00
98d359f59f
markdown issue [skip ci] 2026-06-16 03:49:16 +02:00
96754fd260
better readme [skip ci] 2026-06-16 03:48:47 +02:00
b92b762551
overdid \ [skip ci] 2026-06-16 03:33:03 +02:00
c064e4b1e1
forgot \ [skip ci] 2026-06-16 03:32:46 +02:00
fc33b6fbd5
Update README.md 2026-06-16 03:27:10 +02:00
29e08fe29b
fix double space issue [ci skip] 2026-06-16 03:17:53 +02:00
151666fd21
better excellent enchant fake event [skip ci] 2026-06-16 03:10:34 +02:00
593527241a
fix bugged unit repair & version bump 2026-06-16 02:57:41 +02:00
3578322686
Update README.md 2026-06-13 15:22:39 +02:00
12ec4e1f54
update faststats
fix a potential plugin issue with disabling faststat in a certain way and fix potential submission on first run
2026-06-13 15:14:33 +02:00
b0f32fdba2
do not load metrics in test 2026-06-10 17:18:43 +02:00
d82bd9b22c
do not packet manager on test 2026-06-10 17:07:52 +02:00
380b0de92f
Lot of internal change and monetary cost (#116)
Internal changes this big was not intentional but had to do it for
monetary cost

excluding that: 
- add monetary cost, with dependency on rename
dialogue
- also change some a bit rename dialog
2026-06-10 15:35:41 +02:00
d91576b0de
update faststat [skip ci] 2026-06-10 15:30:57 +02:00
2f9d25bfe9
fix fake prepare anvil on modern versions 2026-06-10 15:19:56 +02:00
49b0054eca
move anvil cost to its own class 2026-06-10 14:59:28 +02:00
2efb6e55e2
new treat anvil event 2026-06-10 14:54:13 +02:00
d679cd73f9
remove rename pdc on paper lore append 2026-06-09 14:08:17 +02:00
9f06f708f5
fix superenchant price 2026-06-08 10:28:03 +02:00
31fa3d38b7
minimum version of datapack tester upped to unsure minimum java version 2026-06-06 12:46:32 +02:00
bf4395ba3f
fix multiples issues 2026-06-03 03:21:45 +02:00
2768c0a0dc
custom craft monetary cost fixed 2026-06-03 03:21:44 +02:00
f0d53a6ffa
Rename Dialog (#113)
Add a rename dialog for longer possible rename text
Also has a rename fix
2026-06-02 17:16:28 +02:00
d0078e528d
fix creative price and other small fixes 2026-06-02 16:50:14 +02:00
edceba879f
custom craft logic deduplication 2026-06-02 16:18:16 +02:00
106bc724a1
deduplicate lore edit logic 2026-06-02 14:36:09 +02:00
e6293be1c6
deduplicate unit repair logic 2026-06-02 14:01:49 +02:00
7a705f3bfc
move a lot of function to AnvilMergeLogic.kt 2026-06-02 13:29:26 +02:00
df92b4bf91
update faststat to 0.24.0 2026-06-01 13:49:15 +02:00
bf8144ad06
result work for unity repair and custom craft 2026-05-30 03:53:31 +02:00
2d31a7f5a8
seems to work better 2026-05-29 13:21:43 +02:00
3992ce1662
no price on no result 2026-05-29 02:48:16 +02:00
7aac325c70
hell 2026-05-29 00:39:12 +02:00
171a8cad6d
monetary cost require dialog rename 2026-05-28 20:33:20 +02:00
fb27ad2e55
avoid looping on same name 2026-05-28 20:11:02 +02:00
1b3447d041
monetary cost display 2026-05-28 17:26:57 +02:00
ac9f492125
monetary minimum version & rename impl 2026-05-28 17:26:56 +02:00
1660250ee1
monetary dependency functions 2026-05-28 17:26:56 +02:00
856c1e08bd
add monetary config and generic progress 2026-05-28 17:26:56 +02:00
2c3e43cb84
moved to vault unlocked 2026-05-28 17:26:56 +02:00
d67380da1a
per type xp cost 2026-05-28 17:26:50 +02:00
b18cf1fd59
hook to vault economy api 2026-05-28 17:26:22 +02:00
bf926fb159
add forgoten default 2026-05-23 17:03:17 +02:00
21087b89e0
force exist when fail to load non default config [skip ci] 2026-05-23 16:55:56 +02:00
a392702df2
negative max dialog size as infinity 2026-05-22 23:57:56 +02:00
5265d81176
add pcd keept rename name 2026-05-22 23:57:56 +02:00
91d2cce8cc
dialog max size range 2026-05-22 23:57:56 +02:00
31f9e7e281
dialog rename working good enough 2026-05-22 23:57:53 +02:00
05951d0ace
don't consider content equal if same name as type. don't cover all case sadly 2026-05-22 23:57:14 +02:00
65bf82a239
add rename dialog options 2026-05-22 23:57:09 +02:00
90cc758e88
Per color code permission (#114)
Add ability to have color code restricted by a specific permission for
each color code
2026-05-22 23:48:33 +02:00
d926b5001e
per color code permission 2026-05-22 23:44:20 +02:00
809dc3488b
fix broken isInTag logic 2026-05-21 23:16:12 +02:00
3594cf72af
try catch other plugin's listeners 2026-05-21 23:15:53 +02:00
2070f8fd68
do not track error
faststats update
2026-05-21 22:53:34 +02:00
68f63a8ec7
fix max damage not being checked 2026-05-21 22:32:04 +02:00
c703dc68f9
add compatibility notice 2026-05-17 17:49:09 +02:00
36030c598b
Add compatibility with Item Adder (#112) 2026-05-17 17:35:59 +02:00
a6cdd79750
check the same way as item adder for eco items 2026-05-17 17:33:51 +02:00
440b2b2741
create negative material set for iterator 2026-05-17 17:30:16 +02:00
41a62d810a
get "all material" info from other plugins 2026-05-17 17:30:16 +02:00
459e3351fd
item adder namespace considered for unit merge 2026-05-17 17:30:16 +02:00
e00c5e8b47
clone use item adder on custom items adder item 2026-05-17 17:30:16 +02:00
638df714fd
Rename .java to .kt 2026-05-17 17:30:16 +02:00
190f334656
prepare to use item adder dependency for clone 2026-05-17 17:30:16 +02:00
8141232c46
add superenchant bulk operation 2026-05-17 17:25:39 +02:00
a1984ad5b9
Superenchant compatibility (#111)
Superenchant compatibility & update kotlin to 2.3
2026-05-17 17:15:38 +02:00
f5343440e4
add super enchant compatibility 2026-05-14 06:30:46 +02:00
55f6b94ba9
add super enchant as dep and update kotlin 2026-05-14 06:29:18 +02:00
24db259435
add super enchant as lib 2026-05-14 06:28:50 +02:00
d867ca6c85
forgot paper version 2026-04-23 14:16:48 +02:00
1b86996317
add 26.1 ver to modrinth versions 2026-04-23 12:32:41 +02:00
bce43649bc
fix build error 2026-04-23 12:28:30 +02:00
12bfcb75ce
add more spigot warnings 2026-04-21 15:19:06 +02:00
e30f09120d
Work with eco item (#106)
Use material key instead of material enum

This is made with the goal of making eco item work as independant item.
so item type key now depend on eco item's id 

Known issue: EcoEnchant target group still do not has the eco item id inside it and cannot be handled properly by custom anvil
2026-04-21 15:18:37 +02:00
b42fb42d83
fix weird isAir error ? 2026-04-21 14:20:17 +02:00
daa1c6171f
check section exist for group & conflict 2026-04-12 20:39:31 +02:00
d4e94872d8
fix recipe error 2026-04-10 12:12:11 +02:00
7612eac765
progress on using namespaced key instead of material 2026-04-10 12:12:06 +02:00
TrashyPixl
1a71086327 ci: use native github actions features for environment variables 2026-03-26 11:41:22 +01:00
45fe037a92
[ci skip] version bump 2026-03-25 11:07:39 +01:00
d061bfc6f4
Revert "[ci skip] try better changelog"
This reverts commit f520d5e3db.
2026-03-25 03:52:18 +01:00
889d452466
Merge remote-tracking branch 'origin/v1.x.x' into v1.x.x 2026-03-25 03:31:11 +01:00
7fbf68dff3
[ci skip] add deprecation warning 2026-03-25 03:30:50 +01:00
f520d5e3db
[ci skip] try better changelog 2026-03-25 02:25:47 +01:00
e5167971f4
[ci skip] faststat ver up 2026-03-24 02:47:06 +01:00
3d50e0ec82
bruh 2026-03-24 01:40:46 +01:00
60ebdbf107
fix bad copy paste 2026-03-24 01:40:22 +01:00
f3c6526967
add excellent enchant limit 2026-03-23 23:54:38 +01:00
8ded2ae9c6
Better enchant limit & remove protocolib (#109)
Simplify item level limit code by deleting "default level" and using
enchantment's default level instead
allow max level 0 for "can't use it" and -1 for "default maximum"
made the enchantment limit gui reflect the above change

Also removed force protocolib in config as hopefully should be
unnecessary. It is still in the plugin in case something go wrong, which
seems unlikely as never been necessary as far as I remember
2026-03-23 20:40:52 +01:00
f0d2f07703
int item display better 2026-03-23 20:06:58 +01:00
26469982b2
indicate alias 2026-03-23 18:26:20 +01:00
882e50e449
remove force protocolib in config 2026-03-23 18:04:14 +01:00
c96dd7d308 simplify and extend enchant limit 2026-03-23 17:57:47 +01:00
f59071f504
prioritize paper nms on paper servers 2026-03-09 22:43:57 +01:00
6afe51acca
reduce faststat java major version 2026-03-09 22:16:35 +01:00
6a4c861eab
update faststat 2026-03-09 21:22:49 +01:00
ab3e4a32ba
[ci skip] version bump 2026-03-03 16:19:59 +01:00
b532ce7dc6
avoid error on java < 21 2026-03-03 16:19:59 +01:00
e08a02a84a
fix class error on java 17 2026-03-03 16:19:59 +01:00
9b8a2d0f32
[ci skip] update permissions in readme 2026-03-03 15:21:40 +01:00
7bb0c1523d
lowercase .md 2026-03-03 15:07:54 +01:00
5caa56be59
lowercase .md 2026-03-03 15:07:38 +01:00
77c8494166
Add last detected error in diag optional 2026-03-03 04:23:07 +01:00
0440835013
Help Command 2026-03-03 04:15:15 +01:00
bc7ed5af85
add permission on tab completer and pre execute 2026-03-03 03:59:58 +01:00
2dd48a8041
Add faststats Telemetry (#108)
also add telemetry option in config.yml
2026-03-03 03:44:59 +01:00
621222fc01
better telemetry disclosure 2026-03-03 03:43:04 +01:00
7044860267
some fix and add disable error telemetry config 2026-03-03 03:12:35 +01:00
48f0cab15d
error logging and bstats simple pie 2026-03-03 00:39:10 +01:00
3e68af06ea
progress on new metrics 2026-03-02 21:58:09 +01:00
d037263e3f
add fast stats as a dependency 2026-03-02 20:42:50 +01:00
5ff096190f code cleanup 2026-03-02 20:27:21 +01:00
39db70d7ad some more logs 2026-03-02 20:27:21 +01:00
ec2384bc7f fix minor feature explaination issue on config 2026-03-02 20:27:21 +01:00
4e15aab024 fix minor error 2026-03-02 20:27:21 +01:00
a66206a52c add conflict before level 2026-03-02 20:27:21 +01:00
2cff7bd83c made combine with logic work 2026-03-02 20:27:21 +01:00
33a86cd3bc version bump 2026-03-02 20:27:21 +01:00
fbc862a5a3 try add conflict after level 2026-03-02 20:27:21 +01:00
20509faed4 add enchantment data 2026-03-02 19:48:34 +01:00
63f2f16b9f add anvil simulation test for diag 2026-03-02 19:48:34 +01:00
63353c6205 progress on diagnostic command 2026-03-02 19:48:34 +01:00
5f707c7397 progress 2026-03-02 19:48:34 +01:00
3eb07a8c09 prepare generic command 2026-03-02 19:48:34 +01:00
196392e206
workflow invert & simplify logic to put not "" first 2026-02-28 15:20:22 +01:00
f13503f873
Feature/better start (#107) 2026-02-28 15:17:02 +01:00
49abca2ccf update checker 2026-02-27 19:46:36 +01:00
d801d85242 better formating 2026-02-22 04:21:14 +01:00
9cf06fbb93 add bstat in credit 2026-02-22 04:15:30 +01:00
c57de03442 add credits move compatibility list & remove spigot link
spigot is not recomended anymore as do not have auto upload
2026-02-22 04:02:20 +01:00
ae8167faec safer start 2026-02-22 01:10:38 +01:00
7aeb776ce0 add run dir for myself 2026-02-22 00:42:33 +01:00
2c30446bc1
don't use eco's pre anvil event player 2026-02-10 12:39:00 +01:00
9ed43f3def
fix eco enchant conflict with everything 2026-02-10 12:14:35 +01:00
8e3f190bb3
version bump 2026-02-10 12:14:04 +01:00
c8f1aa65a2
change message a bit 2026-02-04 17:41:36 +01:00
4dd7d6361b
fix custom anvil not checking enchantment key name
version bump
2026-02-04 17:38:23 +01:00
58b2910350
Fix incompatibility with excellent enchant 5.4 (#105)
Fix #104
2026-01-27 22:09:10 +01:00
76e5059632
use reflection for enchantment definition 2026-01-27 21:28:33 +01:00
b7e19355a8
version bump 2026-01-27 21:05:14 +01:00
377bc4c1d8
use correct anvil combine method 2026-01-27 21:05:03 +01:00
ea6c5724fa
no changelog for build [skip ci] 2026-01-16 21:43:34 +01:00
f14fe20faf
change message a bit again 2026-01-13 02:20:51 +01:00
675a16c9b4
make modrinth publish run every time 2026-01-13 02:09:15 +01:00
18a0f58e68
fix modrinth release 2026-01-13 02:01:24 +01:00
dc7f3f5e20
add modrinth release and fix discord message 2026-01-13 01:24:43 +01:00
73fd79b9da
add release discord webhook 2026-01-12 03:13:49 +01:00
5c32e819fd
pre release specific suffix 2026-01-12 01:45:16 +01:00
4a2a9c5b3a
try multiline 2026-01-12 01:33:51 +01:00
203713385a
invert bad logic 2026-01-12 01:27:45 +01:00
35c67e4207
set env variable early 2026-01-12 01:17:34 +01:00
e1c794403c
print log on build for release debug 2026-01-12 01:05:15 +01:00
69f0e2936e
forgot paper version 2026-01-12 00:57:19 +01:00
be3a98078f
add hangar publish logic 2026-01-12 00:43:06 +01:00
5fe65799c8
cache paperweight 2026-01-10 21:32:13 +01:00
9e0e546367
why did I used path and not name lol 2026-01-10 19:51:45 +01:00
d4165df61a
try add "on release" workflow 2026-01-10 19:40:55 +01:00
4ed9de3d3c
FINALLY offline build SHOULD work 2026-01-10 19:09:03 +01:00
474ad0f1b2
version up 2026-01-01 19:01:56 +01:00
a373cd76f7
has to add adventure as libary for spigot
sadly
2026-01-01 19:01:13 +01:00
a350b7fa69
finally ! smaller jar is smaller 2026-01-01 18:59:48 +01:00
fe2196626a
bring back old gui tester 2026-01-01 17:32:56 +01:00
161ef6ba91
fix forgot 2025-12-30 02:18:11 +01:00
a6cee2d591
check air 2025-12-30 02:03:58 +01:00
139 changed files with 5056 additions and 1350 deletions

View file

@ -12,9 +12,11 @@ on:
branches: [ "v1.x.x", "v2.x.x" ]
pull_request:
branches: [ "v1.x.x", "v2.x.x" ]
release:
types: [published]
concurrency:
group: ${{ github.ref }}
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: true
jobs:
@ -23,6 +25,10 @@ jobs:
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
@ -31,21 +37,41 @@ jobs:
java-version: |
21
distribution: 'temurin'
cache: 'gradle'
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@v4
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
run: ./gradlew build --parallel --stacktrace
# only submit dependency on push
- name: Generate and submit dependency graph
@ -63,7 +89,7 @@ jobs:
echo "ONLINE_JAR_NAME=$(basename $ONLINE_JAR_PATH)" >> $GITHUB_ENV
echo "OFFLINE_JAR_NAME=$(basename $OFFLINE_JAR_PATH)" >> $GITHUB_ENV
# upload the named jars
# upload the named jars as artifact
- name: Upload online JAR artifact
uses: actions/upload-artifact@v4
with:
@ -79,3 +105,59 @@ jobs:
- 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 }}

3
.gitignore vendored
View file

@ -14,6 +14,9 @@
/impl/*/build
/impl/*/.gradle
# run folder
/run/
# other random folders
/htmlReport
/.kotlin/errors

9
.run/Server.run.xml Normal file
View file

@ -0,0 +1,9 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Server" type="JarApplication">
<option name="JAR_PATH" value="$PROJECT_DIR$/run/server.jar" />
<option name="PROGRAM_PARAMETERS" value="-nogui" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/run" />
<option name="ALTERNATIVE_JRE_PATH" />
<method v="2" />
</configuration>
</component>

63
COMPATIBILITY.md Normal file
View file

@ -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.

34
CREDITS.md Normal file
View file

@ -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 ! *

100
README.md
View file

@ -1,18 +1,11 @@
# Custom Anvil
**Custom Anvil** is a plugin that allows server administrators to customize every aspect of the anvil's mechanics.
It is expected to work on 1.18 to 1.21.7 minecraft servers running spigot or paper.
(the plugin support of 1.16.5 to 1.17.1 is experimental and may encounter issues)
**Custom Anvil** was previously named **Unsafe Enchants+**.
It was renamed because it now affects every anvil aspect and not only unsafe enchants\
**Custom Anvil** is based on [Unsafe Enchants](https://github.com/DelilahEve/UnsafeEnchants) by DelilahEve.
### Download Locations:
the plugin can be downloaded on
[Spigot](https://www.spigotmc.org/resources/custom-anvil.114884),
[modrinth](https://modrinth.com/plugin/customanvil),
[Modrinth](https://modrinth.com/plugin/customanvil),
[Hangar](https://hangar.papermc.io/alexcrea/CustomAnvil)
or here [on GitHub](https://github.com/alexcrea/CustomAnvil/releases/latest)
@ -21,7 +14,7 @@ the plugin can be downloaded on
- Vanilla like default configuration.
- Custom enchantment level limit.
- Custom anvil recipes.
- Custom enchant restrictions (allow unsafe enchantment only for a group of item or create new restriction).
- Custom enchant restrictions (allows unsafe enchantment only for a group of item or create new restriction).
- Custom items of unit repairs (repair damaged with unit of "material", for example the repair of diamond sword by diamonds).
- Custom XP cost for every aspect of the anvil.
- Permissions to bypass level limit or enchantment restriction.
@ -30,75 +23,63 @@ the plugin can be downloaded on
- 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)
ca.command.reload: Allow administrator to reload the plugin's configs
ca.config.edit: Allow administrator to edit the plugin's config in game
# Bellow permissions also require some config change to allow usage of features
# usage of these permission is toggleable in basic config gui or config.yml
# 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) (only legacy compatible at the time)
ca.rename.minimessage: Allow player to use minimessage formating on rename if enabled (toggleable)
# Permissions related to edition of the lore
ca.lore_edit.book: Allow player to edit lore via book and quil if enabled (toggleable)
ca.lore_edit.paper: Allow player to edit lore via paper if enabled (toggleable)
# Others
ca.rename.dialog: Allow player to use the rename dialog (toggleable)
```
### Commands
```yml
anvilconfigreload or carl: Reload every config of this plugin
customanvilconfig or configanvil: open a menu for administrator to edit plugin's config in game
```
run `/customanvil help` to get information about available commands \
this only show subcommands you have permission for
### Supported Plugins
Custom Anvil can be compatible with some custom enchantments and anvil mechanics plugins.
Here is a list of supported custom enchantment plugins with support status:
- [Enchantment²](https://www.spigotmc.org/resources/enchants-squared-the-enchantsplus-rewrite-custom-enchantments-that-act-like-vanilla-ones.86747/):
Support by Custom Anvil but still experimental. Automatic configuration.
- [EcoEnchant](https://www.spigotmc.org/resources/ecoenchants-%E2%AD%95-250-enchantments-%E2%9C%85-create-custom-enchants-%E2%9C%A8-essentials-cmi-support.79573/):
Support by Custom Anvil but still experimental. Need to use /anvilconfigreload or a server restart to add newly added enchantment.
Use EcoEnchant restriction system by default.
- [ExcellentEnchants](https://www.spigotmc.org/resources/excellentenchants-%E2%AD%90-75-vanilla-like-enchantments.61693/):
Support by Custom Anvil but still experimental. Use ExcellentEnchants item type.
- [Superenchants](https://modrinth.com/plugin/superenchants)
support by Superenchants. Use CustomAnvil to combine enchantment in anvil in survival.
Here is a list of supported anvil mechanic plugins with support status:
- [Disenchantment](https://www.spigotmc.org/resources/disenchantment-1-21-1-1-20-6-new-book-splitting-mechanics.110741/)
support by Custom Anvil but still experimental. Mostly use Custom Anvil basic XP settings. (version >= 6.1.5)
- [HavenBags](https://www.spigotmc.org/resources/havenbags-shulker-like-player-bound-bags-1-17-1-21-4.110420/)
support by Custom Anvil. Not really enchantment related but CustomAnvil should not impact bag upgrade and skin via anvil. (version >= 1.31.0)
If you like Custom Anvil to support a specific plugin (custom enchant or anvil mechanic).
You can ask, but please note implementing compatibility will be considered
as low priority as I work for the plugin on my free time for free.
See the [Compatibility list](https://github.com/alexcrea/CustomAnvil/blob/v1.x.x/COMPATIBILITY.md)
### Overriding Too Expensive
One of the configurations allow displaying price about 40 and removing Too Expensive. \
By how the minecraft client work: price above 40 can only be displayed green, even if the player does not own enough experience level.
Minecraft version 1.18 to latest marked as supported do not need any ProtocoLib dependency. \
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.
Other version need ProtocoLib enabled on your server for this feature. \
You can also wait for an update of the plugin to support a newer version.
Please note that 1.16.5 to 1.17.1 are not officially supported. Run at your own risk.
But you should wait for update for new version containing new enchantable item or new enchantments if any of this got added.
Else it is, likely, fine to use the current version you are ussing on a new paper version
### For custom enchantment plugin developers
For information about the API, please refer to [the Wiki](https://github.com/alexcrea/CustomAnvil/wiki) \
(Please note that the wiki is currently incomplete)
(Please note that the wiki is currently incomplete)
---
@ -106,12 +87,25 @@ For information about the API, please refer to [the Wiki](https://github.com/ale
see [Here](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs)
---
Custom anvil [use bstat](https://bstats.org/plugin/bukkit/Unsafe%20Enchants%20Plus/20923) for metric. You can [disable it](https://bstats.org/getting-started) if you like.
### Metric And Telemetry
Custom anvil [use bstat](https://bstats.org/plugin/bukkit/Unsafe%20Enchants%20Plus/20923)
and [faststats](https://faststats.dev/project/customanvil/minecraft-plugin) for metric and error reporting.
You can select specific telemetry or disable all in config.yml. \
You can also [disable bstat](https://bstats.org/getting-started) and [faststats](https://faststats.dev/info) in their /plugin folder if you like too.
faststats is in beta testing please report me or them any error you encounter
### Credits and Thanks
Credits and thanks can be seen [here](https://github.com/alexcrea/CustomAnvil/blob/v1.x.x/CREDITS.md)
### Planned:
- Better Folia support (make gui work. fix some dirty handled parts)
- Get restriction on unknown enchantments
- Get restriction on unknown enchantments (planned for V2)
- More features for custom anvil craft
### Known issue:
Most unknown registered enchantments (by unsupported custom enchantment plugin & datapacks) will not have restriction by default. Planned but no eta.
### Do you need help with the plugin, or have any issue or suggestion?
You can ask on the discussion page, create a [GitHub issue](https://github.com/alexcrea/CustomAnvil/issues) or join my [discord](https://discord.gg/KHUNsUfRYJ)

View file

@ -2,10 +2,13 @@ import cn.lalaki.pub.BaseCentralPortalPlusExtension
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import groovy.util.Node
import groovy.util.NodeList
import io.papermc.hangarpublishplugin.model.HangarPublication
import io.papermc.hangarpublishplugin.model.Platforms
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import java.io.ByteArrayOutputStream
plugins {
kotlin("jvm") version "2.1.0"
kotlin("jvm") version "2.3.0"
java
id("org.jetbrains.dokka").version("1.9.20")
id("com.gradleup.shadow").version("9.3.0")
@ -15,13 +18,17 @@ plugins {
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.15.8"
version = "1.17.5"
val isDevBuild = System.getenv("SMALL_COMMIT_HASH") != null
val isPreRelease = System.getenv("IS_GITHUB_PRERELEASE") == "true"
val effectiveVersion = "$version" +
(if (System.getenv("SMALL_COMMIT_HASH") != null) "-dev-${System.getenv("SMALL_COMMIT_HASH")!!}" else "")
(if (isDevBuild) "-dev-${System.getenv("SMALL_COMMIT_HASH")!!}" else "")
repositories {
// EcoEnchants
@ -29,6 +36,18 @@ repositories {
// 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")
@ -38,6 +57,9 @@ dependencies {
// Spigot api
compileOnly("org.spigotmc:spigot-api:1.18-R0.1-SNAPSHOT")
// fast stats
implementation("dev.faststats.metrics:bukkit:0.27.0")
// minimessage
implementation("net.kyori:adventure-text-minimessage:4.25.0")
@ -49,13 +71,17 @@ dependencies {
// EnchantsSquaredRewritten
compileOnly(files("libs/EnchantsSquared.jar"))
// EcoEnchants
compileOnly("com.willfp:EcoEnchants:12.11.1")
// EcoEnchants & item
compileOnly("com.willfp:libreforge:4.79.0:all")
compileOnly("com.willfp:eco:6.74.5")
compileOnly("com.willfp:EcoEnchants:12.11.1")
compileOnly(project(":impl:LegacyEcoEnchant"))
compileOnly("com.willfp:EcoItems:5.66.0")
// ExcellentEnchants
implementation(project(":impl:ExcellentEnchant5_3"))
implementation(project(":impl:ExcellentEnchant5_4"))
compileOnly("su.nightexpress.excellentenchants:Core:5.1.0") {
exclude("org.spigotmc")
}
@ -74,6 +100,15 @@ dependencies {
// 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"))
@ -125,7 +160,7 @@ allprojects {
// Set target version
tasks.withType<JavaCompile>().configureEach {
sourceCompatibility =
"16" // We aim for java 16 for minecraft 1.16.5. even if it not really suported by custom anvil.
"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"
@ -133,34 +168,29 @@ allprojects {
kotlin {
compilerOptions {
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0)
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2)
jvmTarget.set(JvmTarget.JVM_16)
}
}
}
tasks {
// Online jar (use of libraries)
shadowJar {
// No suffix for this jar
val name = "${rootProject.name}-${effectiveVersion}.jar"
fun ShadowJar.configureBaseShadow(suffix: String, libraries: Array<String>) {
val processedSuffix = if(suffix.isEmpty()) "" else "-$suffix"
val name = "${rootProject.name}-${effectiveVersion}${processedSuffix}.jar"
archiveFileName.set(name)
// Exclude kotlin std and its annotation
exclude("**/kotlin-stdlib*.jar")
exclude("**/annotations*.jar")
// Shadow necessary dependency
relocate("com.github.stefvanschie.inventoryframework", "xyz.alexcrea.inventoryframework")
relocate("com.github.stefvanschie.inventoryframework", "xyz.alexcrea.cuanvil.inventoryframework")
relocate("dev.faststats", "xyz.alexcrea.cuanvil.faststats")
// Replace version and example fields in plugin.yml
filesMatching("plugin.yml") {
expand(
"version" to effectiveVersion,
"libraries" to " \"org.jetbrains.kotlin:kotlin-stdlib:2.1.0\" " +
", \"net.kyori:adventure-platform-bukkit:4.4.1\""
"version" to effectiveVersion + processedSuffix,
"libraries" to libraries.joinToString(transform = { "\"$it\"" }),
)
}
@ -168,36 +198,28 @@ tasks {
dependsOn(processResources)
}
// Offline jar (include kotlin std in the final jar fine)
val offlineJar by // Shadow necessary dependency
registering(
// Online jar (use of libraries)
shadowJar {
configureBaseShadow("",
arrayOf(
"org.jetbrains.kotlin:kotlin-stdlib:2.3.0",
"net.kyori:adventure-text-minimessage:4.25.0",
"net.kyori:adventure-text-serializer-plain:4.25.0",
"net.kyori:adventure-text-serializer-legacy:4.25.0",
))
// Include all project other dependencies
ShadowJar
// Exclude kotlin std, annotations and adventure api
exclude("*kotlin/**")
exclude("**/annotations/**")
exclude("net/kyori/**")
}
// Add custom anvil compiled
::class, fun ShadowJar.() {
val name = "${rootProject.name}-${effectiveVersion}-offline.jar"
archiveFileName.set(name)
val offlineJar by registering(ShadowJar::class) {
configureBaseShadow("offline", emptyArray())
// Shadow necessary dependency
relocate("com.github.stefvanschie.inventoryframework", "xyz.alexcrea.inventoryframework")
filesMatching("plugin.yml") {
expand(
"version" to "$effectiveVersion-offline",
"libraries" to ""
)
}
// Include all project other dependencies
from(project.configurations.runtimeClasspath)
// Add custom anvil compiled
from(sourceSets.main.get().output)
dependsOn(processResources)
})
from(sourceSets.main.get().output)
configurations = listOf(project.configurations.runtimeClasspath.get())
}
// Make the online and offline jar on build
named("build") {
@ -333,3 +355,101 @@ publishing {
}
}
}
// 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<ShadowJar>("offlineJar")
jar.set(task.flatMap { it.archiveFile })
// Set platform versions from gradle.properties file
val versions: List<String> = (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")
}
}

View file

@ -3,6 +3,19 @@
# You can still manually edit here if you like to. but if you do, don't forget to /anvilconfigreload after you changes !
#
# What service of metric should custom anvil use
# Custom anvil collect generic information like server minecraft version, type, etc...
# It can also collect error information if error is happening (currently faststats only)
# It can also be disabled
# Please refer to README for public metric link
# Possible options: auto, bstat, faststats, disabled (auto by default)
metric_type: auto
# Allow to report errors made caused by this plugin (only for faststats)
# This allows me to fix potentials issue that I'm not aware of
# Accept true or false (true by default)
metric_collect_errors: true
# All anvil cost will be capped to limit_repair_value if enabled.
#
# In other words:
@ -64,6 +77,18 @@ 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.
@ -74,10 +99,23 @@ permission_needed_for_color: true
# Valid values include 0 to 1000.
use_of_color_cost: 0
# Default limit to apply to any enchants missing from enchant_limits
# Dialogue rename menu make use of dialog menu to allow bigger rename
# You can also change the maximum size and set it to -1 or less for maximum
#
# Valid values include 1 to 1000
default_limit: 5
# This feature only work on paper 1.21.7 or later
#
# At the moment only english is available for this menu... sorry !
#
# CustomAnvil use "ca.rename.dialog" when permission
enable_dialog_rename: false
dialog_rename_max_size: 256
permission_needed_for_dialog_rename: false
# This allows custom anvil to not "guess" the text used for rename but store it in the item
# It will make item stackable only and only if it had used the same rename text
#
# For practical reason. this only work when dialog rename is enabled
dialog_rename_keep_user_text: true
# Override limits for specific enchants
#
@ -85,7 +123,8 @@ default_limit: 5
#
# Overrides provided default from aqua_affinity to depth_strider won't change effect with extra levels
#
# Valid range of 1 - 255 for each enchantment
# Valid range of 0 - 255 for each enchantment
# -1 mean keep default
enchant_limits:
minecraft:aqua_affinity: 1
minecraft:binding_curse: 1
@ -267,7 +306,7 @@ enchant_values:
# Even if disable-merge-over of unbreaking is set to 2
# -1 mean enchantment merge for this enchantment is not disabled. default to -1 if absent.
disable-merge-over:
# Sharpness is set to -1. it equivalent to it not being set to anything (and work as vanilla)
# Sharpness is set to -1. it equivalent to it not being set to anything (and work as vanilla on default configuration)
minecraft:sharpness: -1
# If uncommented. 2 unbreaking II book would not give an unbreaking III book. but unbreaking III book can still be applied
#minecraft:unbreaking: 2
@ -391,16 +430,37 @@ lore_edit:
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
# In case something when wrong with CustomAnvil packet manager.
# If you see "missing class exception" or similar you may test this.
# If enabled and Protocolib absent or disabled "Replace to expensive" will not work.
# ProtocoLib may also be used if the server is in an "unsupported" version even if this option is disabled.
force_protocolib: false
configVersion: 1.11.0

View file

@ -3,6 +3,19 @@
# You can still manually edit here if you like to. but if you do, don't forget to /anvilconfigreload after you changes !
#
# What service of metric should custom anvil use
# Custom anvil collect generic information like server minecraft version, type, etc...
# It can also collect error information if error is happening (currently faststats only)
# It can also be disabled
# Please refer to README for public metric link
# Possible options: auto, bstat, faststats, disabled (auto by default)
metric_type: auto
# Allow to report errors made caused by this plugin (only for faststats)
# This allows me to fix potentials issue that I'm not aware of
# Accept true or false (true by default)
metric_collect_errors: true
# All anvil cost will be capped to limit_repair_value if enabled.
#
# In other words:
@ -66,6 +79,18 @@ 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.
@ -76,10 +101,23 @@ permission_needed_for_color: true
# Valid values include 0 to 1000.
use_of_color_cost: 0
# Default limit to apply to any enchants missing from enchant_limits
# Dialogue rename menu make use of dialog menu to allow bigger rename
# You can also change the maximum size and set it to -1 or less for maximum
#
# Valid values include 1 to 1000
default_limit: 5
# This feature only work on paper 1.21.7 or later
#
# At the moment only english is available for this menu... sorry !
#
# CustomAnvil use "ca.rename.dialog" when permission
enable_dialog_rename: false
dialog_rename_max_size: 256
permission_needed_for_dialog_rename: false
# This allows custom anvil to not "guess" the text used for rename but store it in the item
# It will make item stackable only and only if it had used the same rename text
#
# For practical reason. this only work when dialog rename is enabled
dialog_rename_keep_user_text: true
# Override limits for specific enchants
#
@ -87,7 +125,8 @@ default_limit: 5
#
# Overrides provided default from aqua_affinity to depth_strider won't change effect with extra levels
#
# Valid range of 1 - 255 for each enchantment
# Valid range of 0 - 255 for each enchantment
# -1 mean keep default
enchant_limits:
minecraft:aqua_affinity: 1
minecraft:binding_curse: 1
@ -285,7 +324,7 @@ enchant_values:
# Even if disable-merge-over of unbreaking is set to 2
# -1 mean enchantment merge for this enchantment is not disabled. default to -1 if absent.
disable-merge-over:
# Sharpness is set to -1. it equivalent to it not being set to anything (and work as vanilla)
# Sharpness is set to -1. it equivalent to it not being set to anything (and work as vanilla on default configuration)
minecraft:sharpness: -1
# If uncommented. 2 unbreaking II book would not give an unbreaking III book. but unbreaking III book can still be applied
# minecraft:unbreaking: 2
@ -411,17 +450,38 @@ lore_edit:
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
# In case something when wrong with CustomAnvil packet manager.
# If you see "missing class exception" or similar you may test this.
# If enabled and Protocolib absent or disabled "Replace to expensive" will not work.
# ProtocoLib may also be used if the server is in an "unsupported" version even if this option is disabled.
force_protocolib: false
configVersion: 1.15.5
lowMinecraftVersion: 1.21.11

View file

@ -3,6 +3,19 @@
# You can still manually edit here if you like to. but if you do, don't forget to /anvilconfigreload after you changes !
#
# What service of metric should custom anvil use
# Custom anvil collect generic information like server minecraft version, type, etc...
# It can also collect error information if error is happening (currently faststats only)
# It can also be disabled
# Please refer to README for public metric link
# Possible options: auto, bstat, faststats, disabled (auto by default)
metric_type: auto
# Allow to report errors made caused by this plugin (only for faststats)
# This allows me to fix potentials issue that I'm not aware of
# Accept true or false (true by default)
metric_collect_errors: true
# All anvil cost will be capped to limit_repair_value if enabled.
#
# In other words:
@ -64,6 +77,18 @@ 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.
@ -74,10 +99,23 @@ permission_needed_for_color: true
# Valid values include 0 to 1000.
use_of_color_cost: 0
# Default limit to apply to any enchants missing from enchant_limits
# Dialogue rename menu make use of dialog menu to allow bigger rename
# You can also change the maximum size and set it to -1 or less for maximum
#
# Valid values include 1 to 1000
default_limit: 5
# This feature only work on paper 1.21.7 or later
#
# At the moment only english is available for this menu... sorry !
#
# CustomAnvil use "ca.rename.dialog" when permission
enable_dialog_rename: false
dialog_rename_max_size: 256
permission_needed_for_dialog_rename: false
# This allows custom anvil to not "guess" the text used for rename but store it in the item
# It will make item stackable only and only if it had used the same rename text
#
# For practical reason. this only work when dialog rename is enabled
dialog_rename_keep_user_text: true
# Override limits for specific enchants
#
@ -85,7 +123,8 @@ default_limit: 5
#
# Overrides provided default from aqua_affinity to depth_strider won't change effect with extra levels
#
# Valid range of 1 - 255 for each enchantment
# Valid range of 0 - 255 for each enchantment
# -1 mean keep default
enchant_limits:
minecraft:aqua_affinity: 1
minecraft:binding_curse: 1
@ -279,7 +318,7 @@ enchant_values:
# Even if disable-merge-over of unbreaking is set to 2
# -1 mean enchantment merge for this enchantment is not disabled. default to -1 if absent.
disable-merge-over:
# Sharpness is set to -1. it equivalent to it not being set to anything (and work as vanilla)
# Sharpness is set to -1. it equivalent to it not being set to anything (and work as vanilla on default configuration)
minecraft:sharpness: -1
# If uncommented. 2 unbreaking II book would not give an unbreaking III book. but unbreaking III book can still be applied
# minecraft:unbreaking: 2
@ -403,17 +442,38 @@ lore_edit:
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
# In case something when wrong with CustomAnvil packet manager.
# If you see "missing class exception" or similar you may test this.
# If enabled and Protocolib absent or disabled "Replace to expensive" will not work.
# ProtocoLib may also be used if the server is in an "unsupported" version even if this option is disabled.
force_protocolib: false
configVersion: 1.11.0
lowMinecraftVersion: 1.21.9

View file

@ -3,6 +3,19 @@
# You can still manually edit here if you like to. but if you do, don't forget to /anvilconfigreload after you changes !
#
# What service of metric should custom anvil use
# Custom anvil collect generic information like server minecraft version, type, etc...
# It can also collect error information if error is happening (currently faststats only)
# It can also be disabled
# Please refer to README for public metric link
# Possible options: auto, bstat, faststats, disabled (auto by default)
metric_type: auto
# Allow to report errors made caused by this plugin (only for faststats)
# This allows me to fix potentials issue that I'm not aware of
# Accept true or false (true by default)
metric_collect_errors: true
# All anvil cost will be capped to limit_repair_value if enabled.
#
# In other words:
@ -64,6 +77,18 @@ 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.
@ -74,10 +99,23 @@ permission_needed_for_color: true
# Valid values include 0 to 1000.
use_of_color_cost: 0
# Default limit to apply to any enchants missing from enchant_limits
# Dialogue rename menu make use of dialog menu to allow bigger rename
# You can also change the maximum size and set it to -1 or less for maximum
#
# Valid values include 1 to 1000
default_limit: 5
# This feature only work on paper 1.21.7 or later
#
# At the moment only english is available for this menu... sorry !
#
# CustomAnvil use "ca.rename.dialog" when permission
enable_dialog_rename: false
dialog_rename_max_size: 256
permission_needed_for_dialog_rename: false
# This allows custom anvil to not "guess" the text used for rename but store it in the item
# It will make item stackable only and only if it had used the same rename text
#
# For practical reason. this only work when dialog rename is enabled
dialog_rename_keep_user_text: true
# Override limits for specific enchants
#
@ -85,7 +123,8 @@ default_limit: 5
#
# Overrides provided default from aqua_affinity to depth_strider won't change effect with extra levels
#
# Valid range of 1 - 255 for each enchantment
# Valid range of 0 - 255 for each enchantment
# -1 mean keep default
enchant_limits:
minecraft:aqua_affinity: 1
minecraft:binding_curse: 1
@ -267,7 +306,7 @@ enchant_values:
# Even if disable-merge-over of unbreaking is set to 2
# -1 mean enchantment merge for this enchantment is not disabled. default to -1 if absent.
disable-merge-over:
# Sharpness is set to -1. it equivalent to it not being set to anything (and work as vanilla)
# Sharpness is set to -1. it equivalent to it not being set to anything (and work as vanilla on default configuration)
minecraft:sharpness: -1
# If uncommented. 2 unbreaking II book would not give an unbreaking III book. but unbreaking III book can still be applied
# minecraft:unbreaking: 2
@ -391,16 +430,37 @@ lore_edit:
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
# In case something when wrong with CustomAnvil packet manager.
# If you see "missing class exception" or similar you may test this.
# If enabled and Protocolib absent or disabled "Replace to expensive" will not work.
# ProtocoLib may also be used if the server is in an "unsupported" version even if this option is disabled.
force_protocolib: false
configVersion: 1.11.0

View file

@ -1,5 +1,5 @@
### Default Plugin's Configurations
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 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)

View file

@ -7,3 +7,7 @@ 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

View file

@ -1,21 +0,0 @@
group = rootProject.group
version = rootProject.version
plugins {
kotlin("jvm") version "2.1.0"
}
repositories {
// ExcellentEnchants
maven(url = "https://repo.nightexpressdev.com/releases")
}
dependencies {
// Spigot api
compileOnly("org.spigotmc:spigot-api:1.18-R0.1-SNAPSHOT")
// Excellent Enchant
compileOnly("su.nightexpress.excellentenchants:Core:5.3.0") {
exclude("org.spigotmc")
}
}

View file

@ -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")
}

View file

@ -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();
}
}

View file

@ -2,7 +2,7 @@ group = rootProject.group
version = rootProject.version
plugins {
kotlin("jvm") version "2.1.0"
kotlin("jvm") version "2.3.0"
}
// Imitate needed class and method to support legacy version of EcoEnchant

Binary file not shown.

View file

@ -29,7 +29,7 @@ tasks.withType<JavaCompile>().configureEach {
kotlin {
compilerOptions {
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0)
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2)
jvmTarget.set(JvmTarget.JVM_16)
}
}

View file

@ -1,16 +0,0 @@
package xyz.alexcrea.cuanvil.dependency.gui
import org.bukkit.inventory.InventoryView
interface ExternGuiTester {
fun getContainerClass(view: InventoryView): Class<Any>?
fun testIfGui(inventory: InventoryView): Boolean {
// container class only allow default bukkit craft view or test class
val clazz = getContainerClass(inventory)
return clazz == null
}
}

View file

@ -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
}

View file

@ -11,7 +11,7 @@ dependencies {
implementation(project(":nms:nms-common"))
// Used for nms
paperweight.paperDevBundle("1.20.6-R0.1-SNAPSHOT")
paperweight.paperDevBundle("1.21.7-R0.1-SNAPSHOT")
}
repositories {
@ -29,7 +29,7 @@ tasks.withType<JavaCompile>().configureEach {
kotlin {
compilerOptions {
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0)
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2)
jvmTarget.set(JvmTarget.JVM_18)
}
}

View file

@ -24,8 +24,8 @@ class PaperPacketManager : PacketManagerBase(), PacketManager {
sendedAbilities.mayfly = playerAbilities.mayfly
sendedAbilities.instabuild = instantBuild
sendedAbilities.mayBuild = playerAbilities.mayBuild
sendedAbilities.flyingSpeed = playerAbilities.flyingSpeed
sendedAbilities.walkingSpeed = playerAbilities.walkingSpeed
sendedAbilities.setFlyingSpeed(playerAbilities.getFlyingSpeed())
sendedAbilities.setWalkingSpeed(playerAbilities.getWalkingSpeed())
}
val packet = ClientboundPlayerAbilitiesPacket(sendedAbilities)
nmsPlayer.connection.send(packet)

View file

@ -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<HumanEntity, Component?, String?>,
val keepUserPreviousDialog: Supplier<Boolean>,
val maxLength: Supplier<Int>,
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<UUID, String>()
private val lastRenames = HashMap<UUID, String>()
private val lastLeftItem = HashMap<UUID, String>()
private val runTaskMap = HashMap<UUID, ScheduledTask>()
// For monetary cost
val hasUiOpen = HashSet<UUID>()
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<String?>): 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)
}
}

View file

@ -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<UUID, ScheduledTask>()
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
}
}

View file

@ -29,7 +29,7 @@ tasks.withType<JavaCompile>().configureEach {
kotlin {
compilerOptions {
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0)
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2)
jvmTarget.set(JvmTarget.JVM_16)
}
}

View file

@ -29,7 +29,7 @@ tasks.withType<JavaCompile>().configureEach {
kotlin {
compilerOptions {
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0)
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2)
jvmTarget.set(JvmTarget.JVM_17)
}
}

View file

@ -29,7 +29,7 @@ tasks.withType<JavaCompile>().configureEach {
kotlin {
compilerOptions {
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0)
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2)
jvmTarget.set(JvmTarget.JVM_17)
}
}

View file

@ -29,7 +29,7 @@ tasks.withType<JavaCompile>().configureEach {
kotlin {
compilerOptions {
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0)
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2)
jvmTarget.set(JvmTarget.JVM_17)
}
}

View file

@ -29,7 +29,7 @@ tasks.withType<JavaCompile>().configureEach {
kotlin {
compilerOptions {
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0)
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2)
jvmTarget.set(JvmTarget.JVM_17)
}
}

View file

@ -29,7 +29,7 @@ tasks.withType<JavaCompile>().configureEach {
kotlin {
compilerOptions {
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0)
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2)
jvmTarget.set(JvmTarget.JVM_17)
}
}

View file

@ -29,7 +29,7 @@ tasks.withType<JavaCompile>().configureEach {
kotlin {
compilerOptions {
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0)
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2)
jvmTarget.set(JvmTarget.JVM_18)
}
}

View file

@ -29,7 +29,7 @@ tasks.withType<JavaCompile>().configureEach {
kotlin {
compilerOptions {
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0)
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2)
jvmTarget.set(JvmTarget.JVM_18)
}
}

View file

@ -29,7 +29,7 @@ tasks.withType<JavaCompile>().configureEach {
kotlin {
compilerOptions {
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0)
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2)
jvmTarget.set(JvmTarget.JVM_18)
}
}

View file

@ -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
}
}

View file

@ -29,7 +29,7 @@ tasks.withType<JavaCompile>().configureEach {
kotlin {
compilerOptions {
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0)
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2)
jvmTarget.set(JvmTarget.JVM_21)
}
}

View file

@ -29,7 +29,7 @@ tasks.withType<JavaCompile>().configureEach {
kotlin {
compilerOptions {
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0)
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2)
jvmTarget.set(JvmTarget.JVM_21)
}
}

View file

@ -29,7 +29,7 @@ tasks.withType<JavaCompile>().configureEach {
kotlin {
compilerOptions {
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0)
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2)
jvmTarget.set(JvmTarget.JVM_21)
}
}

View file

@ -28,7 +28,7 @@ tasks.withType<JavaCompile>().configureEach {
kotlin {
compilerOptions {
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0)
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2)
jvmTarget.set(JvmTarget.JVM_21)
}
}

View file

@ -28,7 +28,7 @@ tasks.withType<JavaCompile>().configureEach {
kotlin {
compilerOptions {
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0)
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2)
jvmTarget.set(JvmTarget.JVM_21)
}
}

View file

@ -28,7 +28,7 @@ tasks.withType<JavaCompile>().configureEach {
kotlin {
compilerOptions {
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0)
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2)
jvmTarget.set(JvmTarget.JVM_21)
}
}

View file

@ -28,7 +28,7 @@ tasks.withType<JavaCompile>().configureEach {
kotlin {
compilerOptions {
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0)
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2)
jvmTarget.set(JvmTarget.JVM_21)
}
}

View file

@ -18,5 +18,5 @@ for (nmsPart in reobfNMS) {
// compatibility subprojects
include(":impl:LegacyEcoEnchant")
findProject(":impl:LegacyEcoEnchant")?.name = "LegacyEcoEnchant"
include("impl:ExcellentEnchant5_3")
findProject(":impl:ExcellentEnchant5_3")?.name = "ExcellentEnchant5_3"
include("impl:ExcellentEnchant5_4")
findProject(":impl:ExcellentEnchant5_4")?.name = "ExcellentEnchant5_4"

View file

@ -13,6 +13,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
//TODO add conflict after level
/**
* A Builder for material conflict.
*/

View file

@ -1,6 +1,7 @@
package xyz.alexcrea.cuanvil.api;
import io.delilaheve.CustomAnvil;
import io.delilaheve.util.ConfigOptions;
import org.bukkit.NamespacedKey;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.enchantments.Enchantment;
@ -180,13 +181,13 @@ public class EnchantmentApi {
private static boolean tryWriteDefaultConfig(FileConfiguration defaultConfig, CAEnchantment enchantment, boolean override) {
boolean hasChange = false;
String levelPath = "enchant_limits." + enchantment.getKey();
String levelPath = ConfigOptions.ENCHANT_LIMIT_ROOT + "." + enchantment.getKey();
if(override || !defaultConfig.isSet(levelPath)){
defaultConfig.set(levelPath, enchantment.defaultMaxLevel());
hasChange = true;
}
String basePath = "enchant_values." + enchantment.getKey();
String basePath = ConfigOptions.ENCHANT_VALUES_ROOT + "." + enchantment.getKey();
EnchantmentRarity rarity = enchantment.defaultRarity();
String itemPath = basePath + ".item";

View file

@ -3,6 +3,7 @@ package xyz.alexcrea.cuanvil.api;
import io.delilaheve.CustomAnvil;
import io.delilaheve.util.ConfigOptions;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.configuration.file.FileConfiguration;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -123,7 +124,7 @@ public class MaterialGroupApi {
FileConfiguration config = ConfigHolder.ITEM_GROUP_HOLDER.getConfig();
String basePath = group.getName() + ".";
Set<Material> materialSet = group.getNonGroupInheritedMaterials();
Set<NamespacedKey> materialSet = group.getNonGroupInheritedMaterials();
Set<AbstractMaterialGroup> groupSet = group.getGroups();
boolean empty = true;
@ -153,7 +154,7 @@ public class MaterialGroupApi {
FileConfiguration config = ConfigHolder.ITEM_GROUP_HOLDER.getConfig();
String basePath = group.getName() + ".";
EnumSet<Material> materials = group.getMaterials();
Set<NamespacedKey> materials = group.getMaterials();
if (materials.isEmpty()) return false;
@ -163,8 +164,8 @@ public class MaterialGroupApi {
return true;
}
public static List<String> materialSetToStringList(@NotNull Set<Material> materials) {
return materials.stream().map(material -> material.getKey().getKey().toLowerCase()).toList();
public static List<String> materialSetToStringList(@NotNull Set<NamespacedKey> materials) {
return materials.stream().map(NamespacedKey::toString).toList();
}
public static List<String> materialGroupSetToStringList(@NotNull Set<AbstractMaterialGroup> groups) {

View file

@ -17,7 +17,7 @@ import org.jetbrains.annotations.NotNull;
* Most of the time you would likely need {@link CAPreAnvilBypassEvent} or {@link CAEarlyPreAnvilBypassEvent}
* for this event to be useful.
* <p>
* There is also {@link CATreatAnvilResultEvent} that may be better for some use case.
* There is also {@link CATreatAnvilResult2Event} that may be better for some use case.
*/
public class CAClickResultBypassEvent extends Event implements Cancellable {

View file

@ -15,7 +15,7 @@ import org.jetbrains.annotations.NotNull;
* <p>
* You should also use {@link CAClickResultBypassEvent} if you want to use this event for something useful.
* <p>
* It is also recommended that you read about {@link CAPreAnvilBypassEvent} and {@link CATreatAnvilResultEvent}
* 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 {

View file

@ -18,7 +18,7 @@ import org.jetbrains.annotations.NotNull;
* <p>
* You should also use {@link CAClickResultBypassEvent} if you want to use this event for something useful.
* <p>
* It is also recommended that you read about {@link CAEarlyPreAnvilBypassEvent} and {@link CATreatAnvilResultEvent}
* 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 {

View file

@ -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.
* <p>
* You may also want to check {@link CAClickResultBypassEvent},
* {@link CAPreAnvilBypassEvent}
* and {@link CAEarlyPreAnvilBypassEvent} for your use case
* <p>
* 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.
* <p>
* 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
* <p>
* 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
* <p>
* 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.
* <h3>Important note:</h3>
* the final price are re calculated on click for the following use case:
* <ul>
* <li>Custom craft</li>
* <li>Unit repair</li>
* <li>Lore edit</li>
* </ul>
* This value will be used as final price for:
* <li>Item merge</li>
* <li>Item rename</li>
* </ul>
*
* @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.
* <h3>Important note:</h3>
* the final price are re calculated on click for the following use case:
* <ul>
* <li>Custom craft</li>
* <li>Unit repair</li>
* <li>Lore edit</li>
* </ul>
* This value will be used as final price for:
* <li>Item merge</li>
* <li>Item rename</li>
* </ul>
*
* @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
*
* <h3>Important note:</h3>
* the final price are re calculated on click for the following use case:
* <ul>
* <li>Custom craft</li>
* <li>Unit repair</li>
* <li>Lore edit</li>
* </ul>
* This value will be used as final price for:
* <li>Item merge</li>
* <li>Item rename</li>
*
* @return the current anvil cost
*/
public AnvilCost getCost() {
return cost;
}
}

View file

@ -6,7 +6,8 @@ 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.util.AnvilUseType;
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.
@ -17,8 +18,12 @@ import xyz.alexcrea.cuanvil.util.AnvilUseType;
* and {@link CAEarlyPreAnvilBypassEvent} for your use case
* <p>
* 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();
@ -40,13 +45,13 @@ public class CATreatAnvilResultEvent extends Event {
@Nullable
private ItemStack result;
private int levelCost;
private final AnvilCost cost;
public CATreatAnvilResultEvent(@NotNull PrepareAnvilEvent event, AnvilUseType useType, @Nullable ItemStack result, int levelCost) {
public CATreatAnvilResultEvent(@NotNull PrepareAnvilEvent event, AnvilUseType useType, @Nullable ItemStack result, AnvilCost cost) {
this.event = event;
this.useType = useType;
this.result = result;
this.levelCost = levelCost;
this.cost = cost;
}
/**
@ -104,9 +109,11 @@ public class CATreatAnvilResultEvent extends Event {
* </ul>
*
* @return The current cost.
* @deprecated use #{@link #getCost()} instead
*/
@Deprecated(forRemoval = true, since = "1.17.0")
public int getLevelCost() {
return levelCost;
return cost.asXpCost();
}
/**
@ -124,8 +131,32 @@ public class CATreatAnvilResultEvent extends Event {
* </ul>
*
* @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) {
this.levelCost = 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
*
* <h3>Important note:</h3>
* the final price are re calculated on click for the following use case:
* <ul>
* <li>Custom craft</li>
* <li>Unit repair</li>
* <li>Lore edit</li>
* </ul>
* This value will be used as final price for:
* <li>Item merge</li>
* <li>Item rename</li>
*
* @return the current anvil cost
*/
public AnvilCost getCost() {
return cost;
}
}

View file

@ -9,6 +9,7 @@ import org.jetbrains.annotations.Nullable;
import xyz.alexcrea.cuanvil.group.EnchantConflictManager;
import xyz.alexcrea.cuanvil.group.ItemGroupManager;
import xyz.alexcrea.cuanvil.recipe.CustomAnvilRecipeManager;
import xyz.alexcrea.cuanvil.util.MetricsUtil;
import java.io.File;
import java.io.IOException;
@ -145,6 +146,7 @@ public abstract class ConfigHolder {
sufficientSuccess = true;
} catch (IOException e) {
CustomAnvil.instance.getLogger().log(Level.WARNING, "Could not copy backup saving config " + base.getName(), e);
MetricsUtil.INSTANCE.trackError(e);
}
}
// save last backup
@ -275,6 +277,7 @@ public abstract class ConfigHolder {
this.deletedConfigFile.createNewFile();
} catch (IOException e) {
CustomAnvil.instance.getLogger().log(Level.WARNING, "Could not create " + this.deletedConfigFile.getPath(), e);
MetricsUtil.INSTANCE.trackError(e);
}
loadDeletedListFile(false);
@ -312,6 +315,7 @@ public abstract class ConfigHolder {
this.deletedListConfig.save(this.deletedConfigFile);
} catch (IOException e) {
CustomAnvil.instance.getLogger().log(Level.WARNING, "Could not save " + this.deletedConfigFile.getPath(), e);
MetricsUtil.INSTANCE.trackError(e);
return false;
}

View file

@ -2,7 +2,7 @@ package xyz.alexcrea.cuanvil.config;
import com.google.common.collect.ImmutableMap;
import org.jetbrains.annotations.Nullable;
import xyz.alexcrea.cuanvil.util.AnvilUseType;
import xyz.alexcrea.cuanvil.anvil.AnvilUseType;
import java.util.EnumMap;

View file

@ -1,6 +1,7 @@
package xyz.alexcrea.cuanvil.enchant;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
@ -11,24 +12,23 @@ public interface AdditionalTestEnchantment {
/**
* Test if the provided enchantments can be compatible with this enchantment. only non-Custom Anvil conflict.
* @param enchantments Immutable map of validated enchantments for the item.
* @param itemMat Material of the tested item.
* @param itemType Material namespaced key of the tested item.
* @return If there is a conflict with the enchantments.
*/
boolean isEnchantConflict(
@NotNull Map<CAEnchantment, Integer> enchantments,
@NotNull Material itemMat);
@NotNull NamespacedKey itemType);
/**
* Test if the provided item can be compatible with this enchantment. only non-Custom Anvil conflict.
* @param enchantments Immutable map of validated enchantments for the item.
* @param itemMat Material of the tested item.
* @param itemType Material namespaced key of the tested item.
* @param item Provide a new instance of the used item stack with the partial enchantment applied.
* @return If there is a conflict with the enchantment and the item.
*/
boolean isItemConflict(
@NotNull Map<CAEnchantment, Integer> enchantments,
@NotNull Material itemMat,
@NotNull NamespacedKey itemType,
@NotNull ItemStack item);
}

View file

@ -10,6 +10,7 @@ import xyz.alexcrea.cuanvil.enchant.bulk.BukkitEnchantBulkOperation;
import xyz.alexcrea.cuanvil.enchant.bulk.BulkCleanEnchantOperation;
import xyz.alexcrea.cuanvil.enchant.bulk.BulkGetEnchantOperation;
import xyz.alexcrea.cuanvil.enchant.wrapped.CABukkitEnchantment;
import xyz.alexcrea.cuanvil.util.MetricsUtil;
import java.util.*;
import java.util.logging.Level;
@ -85,11 +86,13 @@ public class CAEnchantmentRegistry {
return false;
}
var error = new IllegalStateException("enchantment " + enchantment.getKey() + " was already registered");
CustomAnvil.instance.getLogger().log(Level.WARNING,
"Duplicate distinct registered enchantment. This should NOT happen any time.\n" +
"If you are a custom anvil developer: Maybe custom anvil detected your enchantment as a bukkit enchantment. " +
"you should maybe remove enchantment with the same key before registering yours",
new IllegalStateException("enchantment " + enchantment.getKey() + " was already registered"));
error);
MetricsUtil.INSTANCE.trackError(error);
return false;
}

View file

@ -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<CAEnchantment, Integer> 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
}
}

View file

@ -1,6 +1,7 @@
package xyz.alexcrea.cuanvil.enchant.wrapped;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import su.nightexpress.excellentenchants.api.enchantment.CustomEnchantment;
@ -39,7 +40,7 @@ public class CAEEPreV5Enchantment extends CABukkitEnchantment implements Additio
}
@Override
public boolean isEnchantConflict(@NotNull Map<CAEnchantment, Integer> enchantments, @NotNull Material itemMat) {
public boolean isEnchantConflict(@NotNull Map<CAEnchantment, Integer> enchantments, @NotNull NamespacedKey itemType) {
if (!definition.hasConflicts()) return false;
Set<String> conflicts = definition.getConflicts();
@ -52,8 +53,8 @@ public class CAEEPreV5Enchantment extends CABukkitEnchantment implements Additio
}
@Override
public boolean isItemConflict(@NotNull Map<CAEnchantment, Integer> enchantments, @NotNull Material itemMat, @NotNull ItemStack item) {
if (Material.ENCHANTED_BOOK.equals(itemMat)) return false;
public boolean isItemConflict(@NotNull Map<CAEnchantment, Integer> enchantments, @NotNull NamespacedKey itemType, @NotNull ItemStack item) {
if (Material.ENCHANTED_BOOK.getKey().equals(itemType)) return false;
return !definition.getSupportedItems().is(item);
}

View file

@ -1,48 +1,51 @@
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 su.nightexpress.excellentenchants.api.wrapper.EnchantDefinition;
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 EnchantDefinition definition;
@NotNull Object definition;
public CAEEV5Enchantment(@NotNull CustomEnchantment enchantment) {
super(enchantment.getBukkitEnchantment(), EnchantmentRarity.getRarity(enchantment.getDefinition().getAnvilCost()));
super(enchantment.getBukkitEnchantment(), EnchantmentRarity.getRarity(getAnvilCost(enchantment)));
this.eeenchantment = enchantment;
this.definition = enchantment.getDefinition();
this.definition = getDefinition(enchantment);
}
@Override
public boolean isEnchantConflict(@NotNull Map<CAEnchantment, Integer> enchantments, @NotNull Material itemMat) {
if (!definition.hasConflicts()) return false;
public boolean isEnchantConflict(@NotNull Map<CAEnchantment, Integer> enchantments, @NotNull NamespacedKey itemType) {
if (!hasConflicts()) return false;
Set<String> conflicts = definition.getExclusiveSet();
Set<String> 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<CAEnchantment, Integer> enchantments, @NotNull Material itemMat, @NotNull ItemStack item) {
if (Material.ENCHANTED_BOOK.equals(itemMat)) return false;
public boolean isItemConflict(@NotNull Map<CAEnchantment, Integer> enchantments, @NotNull NamespacedKey itemType, @NotNull ItemStack item) {
if (Material.ENCHANTED_BOOK.getKey().equals(itemType)) return false;
String key = itemMat.getKey().getKey();
String key = itemType.getKey();
ItemSet primary = eeenchantment.getPrimaryItems();
if (primary.getMaterials().contains(key)) return false;
@ -52,4 +55,74 @@ public class CAEEV5Enchantment extends CABukkitEnchantment implements Additional
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<String> getExclusiveSet() {
try {
return (Set<String>) getExclusiveSetMethod.invoke(definition);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}

View file

@ -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<CAEnchantment, Integer> 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;
}
}

View file

@ -4,6 +4,7 @@ import com.willfp.ecoenchants.enchant.EcoEnchant;
import com.willfp.ecoenchants.target.EnchantmentTarget;
import com.willfp.ecoenchants.type.EnchantmentType;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import xyz.alexcrea.cuanvil.enchant.AdditionalTestEnchantment;
@ -23,9 +24,13 @@ public class CAEcoEnchant extends CABukkitEnchantment implements AdditionalTestE
}
@Override
public boolean isEnchantConflict(@NotNull Map<CAEnchantment, Integer> enchantments, @NotNull Material itemMat) {
public boolean isEnchantConflict(@NotNull Map<CAEnchantment, Integer> enchantments, @NotNull NamespacedKey itemType) {
if (enchantments.isEmpty()) return false;
// Check if there is only self
if (enchantments.size() == 1 && this.equals(enchantments.keySet().stream().findFirst().get()))
return false;
if (this.ecoEnchant.getConflictsWithEverything()) {
return true;
}
@ -57,9 +62,9 @@ public class CAEcoEnchant extends CABukkitEnchantment implements AdditionalTestE
@Override
public boolean isItemConflict(@NotNull Map<CAEnchantment, Integer> enchantments,
@NotNull Material itemMat,
@NotNull NamespacedKey itemType,
@NotNull ItemStack item) {
if (Material.ENCHANTED_BOOK.equals(itemMat)) {
if (Material.ENCHANTED_BOOK.getKey().equals(itemType)) {
return false;
}

View file

@ -1,6 +1,7 @@
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;
@ -24,12 +25,12 @@ public class CAIncompatibleAllEnchant extends CABukkitEnchantment implements Add
@Override
public boolean isEnchantConflict(@NotNull Map<CAEnchantment, Integer> enchantments, @NotNull Material itemMat) {
public boolean isEnchantConflict(@NotNull Map<CAEnchantment, Integer> enchantments, @NotNull NamespacedKey itemType) {
return !enchantments.isEmpty() && !(enchantments.size() == 1 && enchantments.containsKey(this));
}
@Override
public boolean isItemConflict(@NotNull Map<CAEnchantment, Integer> enchantments, @NotNull Material itemMat, @NotNull ItemStack item) {
public boolean isItemConflict(@NotNull Map<CAEnchantment, Integer> enchantments, @NotNull NamespacedKey itemType, @NotNull ItemStack item) {
return false;
}
}

View file

@ -1,6 +1,7 @@
package xyz.alexcrea.cuanvil.enchant.wrapped;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import su.nightexpress.excellentenchants.api.enchantment.EnchantmentData;
@ -22,7 +23,7 @@ public class CALegacyEEEnchantment extends CABukkitEnchantment implements Additi
}
@Override
public boolean isEnchantConflict(@NotNull Map<CAEnchantment, Integer> enchantments, @NotNull Material itemMat) {
public boolean isEnchantConflict(@NotNull Map<CAEnchantment, Integer> enchantments, @NotNull NamespacedKey itemType) {
if (!eeenchantment.hasConflicts()) return false;
Set<String> conflicts = eeenchantment.getConflicts();
@ -35,8 +36,8 @@ public class CALegacyEEEnchantment extends CABukkitEnchantment implements Additi
}
@Override
public boolean isItemConflict(@NotNull Map<CAEnchantment, Integer> enchantments, @NotNull Material itemMat, @NotNull ItemStack item) {
if (Material.ENCHANTED_BOOK.equals(itemMat)) return false;
public boolean isItemConflict(@NotNull Map<CAEnchantment, Integer> enchantments, @NotNull NamespacedKey itemType, @NotNull ItemStack item) {
if (Material.ENCHANTED_BOOK.getKey().equals(itemType)) return false;
return !eeenchantment.getSupportedItems().is(item);
}

View file

@ -4,12 +4,14 @@ import com.willfp.ecoenchants.enchantments.EcoEnchant;
import com.willfp.ecoenchants.enchantments.meta.EnchantmentTarget;
import com.willfp.ecoenchants.enchantments.meta.EnchantmentType;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import xyz.alexcrea.cuanvil.enchant.AdditionalTestEnchantment;
import xyz.alexcrea.cuanvil.enchant.CAEnchantment;
import xyz.alexcrea.cuanvil.enchant.EnchantmentRarity;
import xyz.alexcrea.cuanvil.util.MaterialUtil;
import java.util.Map;
@ -23,7 +25,7 @@ public class CALegacyEcoEnchant extends CABukkitEnchantment implements Additiona
}
@Override
public boolean isEnchantConflict(@NotNull Map<CAEnchantment, Integer> enchantments, @NotNull Material itemMat) {
public boolean isEnchantConflict(@NotNull Map<CAEnchantment, Integer> enchantments, @NotNull NamespacedKey itemType) {
if (enchantments.isEmpty()) return false;
EnchantmentType type = this.ecoEnchant.getType();
@ -48,14 +50,15 @@ public class CALegacyEcoEnchant extends CABukkitEnchantment implements Additiona
@Override
public boolean isItemConflict(@NotNull Map<CAEnchantment, Integer> enchantments,
@NotNull Material itemMat,
@NotNull NamespacedKey itemType,
@NotNull ItemStack item) {
if (Material.ENCHANTED_BOOK.equals(itemMat)) {
if (Material.ENCHANTED_BOOK.getKey().equals(itemType)) {
return false;
}
var mat = MaterialUtil.INSTANCE.getMatFromKey(itemType);
for (EnchantmentTarget target : this.ecoEnchant.getTargets()) {
if (target.getMaterials().contains(itemMat)) {
if (target.getMaterials().contains(mat)) {
return false;
}
}

View file

@ -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<CAEnchantment, Integer> enchantments, @NotNull NamespacedKey itemType) {
var idMap = new HashMap<String, Integer>();
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<CAEnchantment, Integer> enchantments, @NotNull NamespacedKey itemType, @NotNull ItemStack item) {
if(Material.ENCHANTED_BOOK.equals(item.getType())) return false;
return !enchant.canApplyTo(item.getType());
}
}

View file

@ -1,34 +1,35 @@
package xyz.alexcrea.cuanvil.gui.config;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import xyz.alexcrea.cuanvil.util.CasedStringUtil;
import java.util.*;
public interface SelectMaterialContainer {
EnumSet<Material> getSelectedMaterials();
Set<NamespacedKey> getSelectedMaterials();
boolean setSelectedMaterials(EnumSet<Material> materials);
boolean setSelectedMaterials(Set<NamespacedKey> materials);
EnumSet<Material> illegalMaterials();
Set<NamespacedKey> illegalMaterials();
static List<String> getMaterialLore(SelectMaterialContainer container, String containerType, String action){
// Prepare material lore
ArrayList<String> groupLore = new ArrayList<>();
groupLore.add("§7Allow you to select a list of §ematerials §7that this " + containerType + " should " + action);
Set<Material> materialSet = container.getSelectedMaterials();
Set<NamespacedKey> materialSet = container.getSelectedMaterials();
if (materialSet.isEmpty()) {
groupLore.add("§7There is no "+action+"d material for this "+containerType+".");
} else {
groupLore.add("§7List of "+action+"d materials for this "+containerType+":");
Iterator<Material> materialIterator = materialSet.iterator();
Iterator<NamespacedKey> materialIterator = materialSet.iterator();
boolean greaterThanMax = materialSet.size() > 5;
int maxindex = (greaterThanMax ? 4 : materialSet.size());
for (int i = 0; i < maxindex; i++) {
// format string like "- Stone Sword"
String formattedName = CasedStringUtil.snakeToUpperSpacedCase(materialIterator.next().name().toLowerCase());
String formattedName = CasedStringUtil.snakeToUpperSpacedCase(materialIterator.next().getKey().toLowerCase());
groupLore.add("§7- §e" + formattedName);
}

View file

@ -11,6 +11,7 @@ import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull;
import xyz.alexcrea.cuanvil.gui.util.GuiGlobalActions;
import xyz.alexcrea.cuanvil.gui.util.GuiSharedConstant;
import xyz.alexcrea.cuanvil.util.MetricsUtil;
import java.util.Arrays;
import java.util.function.Supplier;
@ -41,6 +42,7 @@ public class ConfirmActionGui extends AbstractAskGui {
success = onConfirm.get();
} catch (Exception e) {
CustomAnvil.instance.getLogger().log(Level.WARNING, "Could not process confirmation supplier.", e);
MetricsUtil.INSTANCE.trackError(e);
success = false;
}

View file

@ -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){

View file

@ -14,6 +14,7 @@ import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import xyz.alexcrea.cuanvil.config.ConfigHolder;
import xyz.alexcrea.cuanvil.dependency.MinecraftVersionUtil;
import xyz.alexcrea.cuanvil.dependency.packet.PacketManager;
import xyz.alexcrea.cuanvil.gui.ValueUpdatableGui;
import xyz.alexcrea.cuanvil.gui.config.MainConfigGui;
@ -283,7 +284,7 @@ public class BasicConfigGui extends ChestGui implements ValueUpdatableGui {
if(!this.packetManager.getCanSetInstantBuild()){
lore.add("");
lore.add("§4/!\\§cCaution§4/!\\ §cYou need ProtocoLib installed and working or a newer version of this plugin for this to work.");
lore.add("§4/!\\§cCaution§4/!\\ §cYou need ProtocoLib installed and working or a paper server.");
lore.add("§cCurrently ProtocoLib is not detected.");
}

View file

@ -17,7 +17,7 @@ import java.util.Locale;
*/
public class EnchantLimitConfigGui extends AbstractEnchantConfigGui<IntSettingsGui.IntSettingFactory> {
private static final String SECTION_NAME = "enchant_limits";
private static final String SECTION_NAME = ConfigOptions.ENCHANT_LIMIT_ROOT;
private static EnchantLimitConfigGui INSTANCE = null;
@ -41,18 +41,34 @@ public class EnchantLimitConfigGui extends AbstractEnchantConfigGui<IntSettingsG
String key = enchant.getKey().toString().toLowerCase(Locale.ROOT);
String prettyKey = CasedStringUtil.snakeToUpperSpacedCase(key.replace(":", "_"));
var defaultValue = enchant.defaultMaxLevel();
return new IntSettingsGui.IntSettingFactory(prettyKey + " Limit", this,
SECTION_NAME + '.' + key, ConfigHolder.DEFAULT_CONFIG,
Collections.singletonList(
"§7Maximum applied level of " + prettyKey
),
0, 255,
enchant.defaultMaxLevel(),
-1, 255, -1,
1, 5, 10, 50, 100){
@Override
public int getConfiguredValue() {
return ConfigOptions.INSTANCE.enchantLimit(enchant);
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);
}
};
}

View file

@ -25,6 +25,7 @@ import xyz.alexcrea.cuanvil.gui.util.GuiGlobalActions;
import xyz.alexcrea.cuanvil.gui.util.GuiGlobalItems;
import xyz.alexcrea.cuanvil.gui.util.GuiSharedConstant;
import xyz.alexcrea.cuanvil.util.CasedStringUtil;
import xyz.alexcrea.cuanvil.util.MetricsUtil;
import java.util.*;
import java.util.function.Supplier;
@ -264,6 +265,7 @@ public class EnchantConflictSubSettingGui extends MappedToListSubSettingGui impl
updateGuiValues();
} catch (Exception e) {
CustomAnvil.instance.getLogger().log(Level.WARNING, "An error occurred while updating enchants for " + this.enchantConflict, e);
MetricsUtil.INSTANCE.trackError(e);
}
// Save file configuration to disk
@ -308,6 +310,7 @@ public class EnchantConflictSubSettingGui extends MappedToListSubSettingGui impl
updateGuiValues();
} catch (Exception e) {
CustomAnvil.instance.getLogger().log(Level.WARNING, "An error occurred while updating group for " + this.enchantConflict, e);
MetricsUtil.INSTANCE.trackError(e);
}
// Save file configuration to disk

View file

@ -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;
@ -325,19 +326,19 @@ public class GroupConfigSubSettingGui extends MappedToListSubSettingGui implemen
// ----------------------------
@Override
public EnumSet<Material> getSelectedMaterials() {
public Set<NamespacedKey> getSelectedMaterials() {
return this.group.getNonGroupInheritedMaterials();
}
@Override
public boolean setSelectedMaterials(EnumSet<Material> materials) {
public boolean setSelectedMaterials(Set<NamespacedKey> 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);
@ -353,8 +354,8 @@ public class GroupConfigSubSettingGui extends MappedToListSubSettingGui implemen
}
@Override
public EnumSet<Material> illegalMaterials() {
return EnumSet.of(Material.AIR);
public Set<NamespacedKey> illegalMaterials() {
return Set.of(Material.AIR.getKey());
}
// ----------------------------

View file

@ -72,7 +72,8 @@ public class IntSettingsGui extends AbstractSettingGui {
assert meta != null;
meta.setDisplayName("§eReset to default value");
meta.setLore(Collections.singletonList("§7Default value is §e" + holder.defaultVal));
meta.setLore(Collections.singletonList("§7Default value is §e" +
holder.valueDisplayName(ValueDisplayType.RESET, holder.defaultVal)));
item.setItemMeta(meta);
returnToDefault = new GuiItem(item, event -> {
event.setCancelled(true);
@ -86,41 +87,23 @@ public class IntSettingsGui extends AbstractSettingGui {
* Update item using the setting value to match the new value.
*/
protected void updateValueDisplay() {
PatternPane pane = getPane();
// minus item
GuiItem minusItem;
if (now > holder.min) {
int planned = Math.max(holder.min, now - step);
ItemStack item = new ItemStack(Material.RED_TERRACOTTA);
ItemMeta meta = item.getItemMeta();
assert meta != null;
meta.setDisplayName("§e" + now + " §f-> §e" + planned + " §r(§c-" + (now - planned) + "§r)");
meta.setLore(Collections.singletonList(AbstractSettingGui.CLICK_LORE));
item.setItemMeta(meta);
minusItem = new GuiItem(item, updateNowConsumer(planned), CustomAnvil.instance);
minusItem = valueEditItem(Material.RED_TERRACOTTA, ValueDisplayType.REMOVE, planned);
} else {
minusItem = GuiGlobalItems.backgroundItem(Material.BARRIER);
}
pane.bindItem('-', minusItem);
//plus item
// may do a function to generalise ?
GuiItem plusItem;
if (now < holder.max) {
int planned = Math.min(holder.max, now + step);
ItemStack item = new ItemStack(Material.GREEN_TERRACOTTA);
ItemMeta meta = item.getItemMeta();
assert meta != null;
meta.setDisplayName("§e" + now + " §f-> §e" + planned + " §r(§a+" + (planned - now) + "§r)");
meta.setLore(Collections.singletonList(AbstractSettingGui.CLICK_LORE));
item.setItemMeta(meta);
plusItem = new GuiItem(item, updateNowConsumer(planned), CustomAnvil.instance);
plusItem = valueEditItem(Material.GREEN_TERRACOTTA, ValueDisplayType.ADD, planned);
} else {
plusItem = GuiGlobalItems.backgroundItem(Material.BARRIER);
}
@ -131,7 +114,7 @@ public class IntSettingsGui extends AbstractSettingGui {
ItemMeta resultMeta = resultPaper.getItemMeta();
assert resultMeta != null;
resultMeta.setDisplayName("§fValue: §e" + now);
resultMeta.setDisplayName("§fValue: §e" + holder.valueDisplayName(ValueDisplayType.CURRENT, now));
resultMeta.setLore(holder.displayLore);
resultPaper.setItemMeta(resultMeta);
@ -149,7 +132,21 @@ public class IntSettingsGui extends AbstractSettingGui {
}
pane.bindItem('D', returnToDefault);
}
private GuiItem valueEditItem(Material mat, ValueDisplayType type, int planned) {
ItemStack item = new ItemStack(mat);
ItemMeta meta = item.getItemMeta();
assert meta != null;
var nowDisplay = holder.valueDisplayName(type, now);
var plannedDisplay = holder.valueDisplayName(type, planned);
var deltaDisplay = holder.deltaDisplay(type, now, planned);
meta.setDisplayName("§e" + nowDisplay + " §f-> §e" + plannedDisplay + " §r(§c" + deltaDisplay + "§r)");
meta.setLore(Collections.singletonList(AbstractSettingGui.CLICK_LORE));
item.setItemMeta(meta);
return new GuiItem(item, updateNowConsumer(planned), CustomAnvil.instance);
}
/**
@ -389,6 +386,23 @@ public class IntSettingsGui extends AbstractSettingGui {
return getItem(itemMat, CasedStringUtil.detectToUpperSpacedCase(configPath));
}
protected String valueDisplayName(ValueDisplayType type, int value) {
return String.valueOf(value);
}
protected String deltaDisplay(ValueDisplayType type, int now, int planned) {
var delta = planned - now;
if(delta < 0) return "§c" + delta;
else return "§a+" + delta;
}
}
public enum ValueDisplayType {
ADD,
CURRENT,
REMOVE,
RESET,
}
}

View file

@ -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<Material, GuiItem> {
public class MaterialSelectSettingGui extends MappedElementListConfigGui<NamespacedKey, GuiItem> {
private final SelectMaterialContainer selector;
private final Gui backGui;
private boolean instantRemove;
private final List<Material> defaultMaterials;
private final EnumSet<Material> illegalMaterials;
private final List<NamespacedKey> defaultMaterials;
private final Set<NamespacedKey> illegalMaterials;
private final int defaultMaterialHash;
private int nowMaterialHash;
@ -161,8 +163,7 @@ public class MaterialSelectSettingGui extends MappedElementListConfigGui<Materia
// Save setting
EnumSet<Material> result = EnumSet.noneOf(Material.class);
result.addAll(this.elementGuiMap.keySet());
Set<NamespacedKey> result = new HashSet<>(this.elementGuiMap.keySet());
if(!this.selector.setSelectedMaterials(result)){
player.sendMessage("§cSomething went wrong while saving the change of value.");
@ -185,8 +186,8 @@ public class MaterialSelectSettingGui extends MappedElementListConfigGui<Materia
ItemStack cursor = player.getItemOnCursor();
// Test if cursor material allowed
Material cursorMat = cursor.getType();
if(cursorMat.isAir()) return;
NamespacedKey cursorMat = MaterialUtil.INSTANCE.getCustomType(cursor);
if(MaterialUtil.INSTANCE.isAir(cursorMat)) return;
if(this.illegalMaterials.contains(cursorMat)) return;
// Update gui only if item did not exist before.
@ -201,12 +202,12 @@ public class MaterialSelectSettingGui extends MappedElementListConfigGui<Materia
}
@Override
protected ItemStack createItemForGeneric(Material material) {
ItemStack item = new ItemStack(material);
protected ItemStack createItemForGeneric(NamespacedKey material) {
ItemStack item = new ItemStack(Objects.requireNonNull(MaterialUtil.INSTANCE.getMatFromKey(material)));
ItemMeta meta = item.getItemMeta();
if(meta == null) return item;
meta.setDisplayName("§a" + CasedStringUtil.snakeToUpperSpacedCase(material.name().toLowerCase()));
meta.setDisplayName("§a" + CasedStringUtil.snakeToUpperSpacedCase(material.getKey().toLowerCase()));
meta.setLore(Collections.singletonList("§7Click here to remove this material from the list"));
meta.addItemFlags(ItemFlag.values());
@ -216,22 +217,22 @@ public class MaterialSelectSettingGui extends MappedElementListConfigGui<Materia
}
@Override
protected Collection<Material> getEveryDisplayableInstanceOfGeneric() {
protected Collection<NamespacedKey> getEveryDisplayableInstanceOfGeneric() {
return this.defaultMaterials;
}
@Override
protected void updateElement(Material material, GuiItem element) {
protected void updateElement(NamespacedKey material, GuiItem element) {
// Nothing happen here I think
}
@Override
protected GuiItem newElementRequested(Material material, GuiItem newItem) {
protected GuiItem newElementRequested(NamespacedKey material, GuiItem newItem) {
newItem.setAction(event -> {
if(this.instantRemove){
removeMaterial(material);
}else {
String materialName = CasedStringUtil.snakeToUpperSpacedCase(material.name().toLowerCase());
String materialName = CasedStringUtil.snakeToUpperSpacedCase(material.getKey().toLowerCase());
// Create and show confirm remove gui.
ConfirmActionGui confirmGui = new ConfirmActionGui(
@ -250,7 +251,7 @@ public class MaterialSelectSettingGui extends MappedElementListConfigGui<Materia
return newItem;
}
private void removeMaterial(Material material) {
private void removeMaterial(NamespacedKey material) {
if(this.elementGuiMap.containsKey(material)){
this.nowMaterialHash ^= material.hashCode();
setSaveItem();
@ -260,18 +261,18 @@ public class MaterialSelectSettingGui extends MappedElementListConfigGui<Materia
}
@Override
protected GuiItem findItemFromElement(Material generic, GuiItem element) {
protected GuiItem findItemFromElement(NamespacedKey generic, GuiItem element) {
return element;
}
@Override
protected GuiItem findGuiItemForRemoval(Material generic, GuiItem element) {
protected GuiItem findGuiItemForRemoval(NamespacedKey generic, GuiItem element) {
return element;
}
private static int hashFromMaterialList(List<Material> materialList){
private static int hashFromMaterialList(List<NamespacedKey> materialList){
int defaultMaterialHash = 0;
for (Material material : materialList) {
for (NamespacedKey material : materialList) {
defaultMaterialHash ^= material.hashCode();
}
return defaultMaterialHash;

View file

@ -11,11 +11,11 @@ import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull;
import xyz.alexcrea.cuanvil.anvil.AnvilUseType;
import xyz.alexcrea.cuanvil.config.ConfigHolder;
import xyz.alexcrea.cuanvil.config.WorkPenaltyType;
import xyz.alexcrea.cuanvil.gui.config.global.BasicConfigGui;
import xyz.alexcrea.cuanvil.gui.util.GuiGlobalActions;
import xyz.alexcrea.cuanvil.util.AnvilUseType;
import java.util.ArrayList;
import java.util.EnumMap;

View file

@ -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<Exception> onError = null;
@Nullable
public Function<String, String> 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<String> 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<String, String> 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<String, String> prepareParameters(){
var parameters = new HashMap<String, String>();
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<Exception> 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<String, String> getRawVersion) {
this.getRawVersion = getRawVersion;
return this;
}
}

View file

@ -5,6 +5,7 @@ import io.delilaheve.util.ConfigOptions;
import org.bukkit.configuration.file.FileConfiguration;
import org.jetbrains.annotations.NotNull;
import xyz.alexcrea.cuanvil.config.ConfigHolder;
import xyz.alexcrea.cuanvil.util.MetricType;
import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil;
import xyz.alexcrea.cuanvil.util.config.LoreEditType;
@ -18,6 +19,9 @@ public class PluginSetDefault {
int nbSet = 0;
nbSet += trySetDefault(config, METRIC_TYPE, MetricType.AUTO.getValue());
nbSet += trySetDefault(config, METRIC_COLLECT_ERROR, true);
nbSet += trySetDefault(config, CAP_ANVIL_COST, DEFAULT_CAP_ANVIL_COST);
nbSet += trySetDefault(config, MAX_ANVIL_COST, DEFAULT_MAX_ANVIL_COST);
nbSet += trySetDefault(config, REMOVE_ANVIL_COST_LIMIT, DEFAULT_REMOVE_ANVIL_COST_LIMIT);
@ -30,7 +34,7 @@ public class PluginSetDefault {
nbSet += trySetDefault(config, ALLOW_HEXADECIMAL_COLOR, DEFAULT_ALLOW_HEXADECIMAL_COLOR);
nbSet += trySetDefault(config, PERMISSION_NEEDED_FOR_COLOR, DEFAULT_PERMISSION_NEEDED_FOR_COLOR);
nbSet += trySetDefault(config, USE_OF_COLOR_COST, DEFAULT_USE_OF_COLOR_COST);
nbSet += trySetDefault(config, DEFAULT_LIMIT_PATH, DEFAULT_ENCHANT_LIMIT);
nbSet += trySetDefault(config, PER_COLOR_CODE_PERMISSION, DEFAULT_PER_COLOR_CODE_PERMISSION);
// Lore Edit defaults
for (@NotNull LoreEditType value : LoreEditType.values()) {
@ -57,6 +61,11 @@ public class PluginSetDefault {
nbSet += trySetDefault(config, PAPER_EDIT_ORDER, DEFAULT_PAPER_EDIT_ORDER);
nbSet += trySetDefault(config, DIALOG_RENAME_ENABLED, DEFAULT_DIALOG_RENAME_ENABLED);
nbSet += trySetDefault(config, DIALOG_MAX_SIZE, DEFAULT_DIALOG_MAX_SIZE);
nbSet += trySetDefault(config, DIALOG_RENAME_USE_PERMISSION, DEFAULT_DIALOG_RENAME_USE_PERMISSION);
nbSet += trySetDefault(config, DIALOG_KEEP_USER_TEXT, DEFAULT_DIALOG_KEEP_USER_TEXT);
if (nbSet > 0) {
CustomAnvil.instance.getLogger().info("Adding " + nbSet + " absent default config values.");
ConfigHolder.DEFAULT_CONFIG.saveToDisk(true);

View file

@ -77,6 +77,12 @@ public class UpdateHandler {
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<ConfigHolder> toSave) {

View file

@ -15,22 +15,6 @@ public class UpdateUtils {
return Version.fromString(versionString);
}
@Deprecated
public static int[] currentMinecraftVersionArray() {
String versionString = Bukkit.getServer().getBukkitVersion().split("-")[0];
return UpdateUtils.readVersionFromString(versionString);
}
public static int[] readVersionFromString(String versionString) {
String[] partialVersion = versionString.split("\\.");
int[] versionParts = new int[]{0, 0, 0};
for (int i = 0; i < Math.min(3, partialVersion.length); i++) {
versionParts[i] = Integer.parseInt(partialVersion[i]);
}
return versionParts;
}
public static void addToStringList(FileConfiguration config, String path, String... toAdd) {
List<String> groups = new ArrayList<>(config.getStringList(path));
groups.addAll(Arrays.asList(toAdd));

View file

@ -21,7 +21,11 @@ public record Version(int major, int minor, int patch) {
int[] versionParts = new int[]{0, 0, 0};
for (int i = 0; i < Math.min(3, partialVersion.length); i++) {
versionParts[i] = Integer.parseInt(partialVersion[i]);
try {
versionParts[i] = Integer.parseInt(partialVersion[i]);
} catch (NumberFormatException e) {
break;
}
}
return new Version(versionParts[0], versionParts[1], versionParts[2]);
}

View file

@ -1,6 +1,7 @@
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;
@ -11,6 +12,7 @@ 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;
@ -69,7 +71,12 @@ public class PUpdate_1_11_0 {
// Create new group
IncludeGroup group = new IncludeGroup(toolset);
group.addAll(toolMats);
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);
@ -77,8 +84,8 @@ public class PUpdate_1_11_0 {
if (tools == null) return;
if (!(tools instanceof IncludeGroup include)) return;
List<Material> mats = List.of(toolMats);
Set<Material> matSet = include.getNonGroupInheritedMaterials();
List<NamespacedKey> mats = List.of(keys);
Set<NamespacedKey> matSet = include.getNonGroupInheritedMaterials();
if (!matSet.containsAll(mats)) return;
mats.forEach(matSet::remove);

View file

@ -2,10 +2,10 @@ package xyz.alexcrea.cuanvil.update.plugin;
import io.delilaheve.util.ConfigOptions;
import org.bukkit.configuration.file.FileConfiguration;
import xyz.alexcrea.cuanvil.anvil.AnvilUseType;
import xyz.alexcrea.cuanvil.config.ConfigHolder;
import xyz.alexcrea.cuanvil.config.WorkPenaltyType;
import xyz.alexcrea.cuanvil.gui.config.settings.WorkPenaltyTypeSettingGui;
import xyz.alexcrea.cuanvil.util.AnvilUseType;
import javax.annotation.Nonnull;
import java.util.EnumMap;

View file

@ -6,10 +6,13 @@ import org.bukkit.configuration.file.YamlConfiguration
import org.bukkit.plugin.java.JavaPlugin
import xyz.alexcrea.cuanvil.api.event.CAConfigReadyEvent
import xyz.alexcrea.cuanvil.api.event.CAEnchantRegistryReadyEvent
import xyz.alexcrea.cuanvil.command.CustomAnvilCommand
import xyz.alexcrea.cuanvil.command.EditConfigExecutor
import xyz.alexcrea.cuanvil.command.ReloadExecutor
import xyz.alexcrea.cuanvil.config.ConfigHolder
import xyz.alexcrea.cuanvil.dependency.DependencyManager
import xyz.alexcrea.cuanvil.dependency.MinecraftVersionUtil
import xyz.alexcrea.cuanvil.dependency.economy.EconomyManager
import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil
import xyz.alexcrea.cuanvil.enchant.CAEnchantmentRegistry
import xyz.alexcrea.cuanvil.gui.config.MainConfigGui
@ -18,9 +21,10 @@ import xyz.alexcrea.cuanvil.listener.AnvilCloseListener
import xyz.alexcrea.cuanvil.listener.AnvilResultListener
import xyz.alexcrea.cuanvil.listener.ChatEventListener
import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener
import xyz.alexcrea.cuanvil.update.ModrinthUpdateChecker
import xyz.alexcrea.cuanvil.update.PluginSetDefault
import xyz.alexcrea.cuanvil.update.UpdateHandler
import xyz.alexcrea.cuanvil.util.Metrics
import xyz.alexcrea.cuanvil.util.MetricsUtil
import java.io.File
import java.io.FileReader
import java.util.logging.Level
@ -31,8 +35,8 @@ import java.util.logging.Level
open class CustomAnvil : JavaPlugin() {
companion object {
// bstats plugin id
private const val bstatsPluginId = 20923
// pluginIDS
private const val modrinthPluginID = "S75Ueiq9"
// Permission string required to use the plugin's features
const val affectedByPluginPermission = "ca.affected"
@ -46,9 +50,13 @@ open class CustomAnvil : JavaPlugin() {
// Permission string required to reload the config
const val commandReloadPermission = "ca.command.reload"
// Permission string required to get diagnostic data
const val diagnosticPermission = "ca.command.diagnostic"
// Permission string required to edit the plugin's config
const val editConfigPermission = "ca.config.edit"
// Command Name to reload the config
const val commandReloadName = "anvilconfigreload"
@ -61,6 +69,8 @@ open class CustomAnvil : JavaPlugin() {
// Chat message listener
lateinit var chatListener: ChatEventListener
var latestVer: String? = null
/**
* Logging handler
*/
@ -81,12 +91,99 @@ open class CustomAnvil : JavaPlugin() {
}
// stop plugin if we do not force a dirty start (true by default)
// Return true if start was stopped
private fun tryDirtyStart(): Boolean {
if(!ConfigHolder.DEFAULT_CONFIG.config.getBoolean("dirty_start", false)) {
Bukkit.getPluginManager().disablePlugin(this)
return true
}
return false
}
// stop plugin if we force a safe start (false by default)
// Return true if start was stopped
private fun trySafeStart(): Boolean {
if(ConfigHolder.DEFAULT_CONFIG.config.getBoolean("safe_start", false)) {
Bukkit.getPluginManager().disablePlugin(this)
return true
}
return false
}
/**
* Setup plugin for use
*/
override fun onEnable() {
instance = this
try {
legacyCheck()
} catch (e: Exception) {
logger.log(Level.SEVERE, "error trying to check for legacy system", e)
MetricsUtil.trackError(e)
if(trySafeStart()) return
}
// Add commands
try {
prepareCommand()
} catch (e: Exception) {
logger.log(Level.SEVERE, "error trying to register commands", e)
MetricsUtil.trackError(e)
if(trySafeStart()) return
}
// Load default configuration
try {
if(!ConfigHolder.loadDefaultConfig())
throw RuntimeException("Error loading configuration file")
} catch (e: Exception) {
logger.log(Level.SEVERE, "error occurred loading default configuration", e)
MetricsUtil.trackError(e)
if(tryDirtyStart()) return
}
// Load dependency
try {
DependencyManager.loadDependency()
} catch (e: Exception) {
logger.log(Level.SEVERE, "error loading dependency compatibility", e)
MetricsUtil.trackError(e)
if(tryDirtyStart()) return
}
// Register listeners
try {
registerListeners()
} catch (e: Exception) {
logger.log(Level.SEVERE, "error registering listeners", e)
MetricsUtil.trackError(e)
if(tryDirtyStart()) return
}
// Load metrics
MetricsUtil.loadMetrics(this)
// Load other thing later.
// It is so other dependent plugins can implement there event listener before we fire them.
DependencyManager.scheduler.scheduleGlobally(this) { loadEnchantmentSystemDirty() }
}
override fun onDisable() {
MetricsUtil.shutdownMetrics()
}
private fun loadEnchantmentSystemDirty() {
try {
loadEnchantmentSystem()
} catch (e: Exception) {
logger.log(Level.SEVERE, "error initializing enchantment system", e)
MetricsUtil.trackError(e)
tryDirtyStart()
}
}
private fun legacyCheck() {
// Disable old plugin name if exist
val potentialPlugin = Bukkit.getPluginManager().getPlugin("UnsafeEnchantsPlus")
if (potentialPlugin != null) {
@ -95,38 +192,43 @@ open class CustomAnvil : JavaPlugin() {
logger.warning("Please note CustomAnvil is a more recent version of UnsafeEnchantsPlus")
}
if(!PlatformUtil.isPaper) {
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")
}
}
// Add commands
prepareCommand()
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()
server.pluginManager.registerEvents(chatListener, this)
// Load default configuration
if (!ConfigHolder.loadDefaultConfig()) {
logger.log(Level.SEVERE,"could not load default config.")
return
}
// Load dependency
DependencyManager.loadDependency()
// Register anvil events
server.pluginManager.registerEvents(PrepareAnvilListener(), this)
server.pluginManager.registerEvents(AnvilResultListener(), this)
server.pluginManager.registerEvents(AnvilCloseListener(DependencyManager.packetManager), this)
// Load metrics
Metrics(this, bstatsPluginId)
// Load other thing later.
// It is so other dependent plugins can implement there event listener before we fire them.
DependencyManager.scheduler.scheduleGlobally(this, {loadEnchantmentSystem()})
}
private fun loadEnchantmentSystem(){
@ -139,7 +241,8 @@ open class CustomAnvil : JavaPlugin() {
// Load config
if (!ConfigHolder.loadNonDefaultConfig()) {
logger.log(Level.SEVERE,"could not load non default config.")
logger.log(Level.SEVERE,"Plugin has an issue while trying to load non default config... exiting...")
server.pluginManager.disablePlugin(this)
return
}
@ -157,9 +260,11 @@ open class CustomAnvil : JavaPlugin() {
MainConfigGui.getInstance().init(DependencyManager.packetManager)
GuiSharedConstant.loadConstants()
// Prepare economy if possible
EconomyManager.setupEconomy(this)
// Finally, re add default we may be missing
PluginSetDefault.reAddMissingDefault()
}
fun reloadResource(
@ -211,6 +316,8 @@ open class CustomAnvil : JavaPlugin() {
command = getCommand(commandConfigName)
command?.setExecutor(EditConfigExecutor())
CustomAnvilCommand(this)
}
}

View file

@ -2,14 +2,17 @@ package io.delilaheve.util
import io.delilaheve.CustomAnvil
import io.delilaheve.util.EnchantmentUtil.enchantmentName
import org.bukkit.Material
import org.bukkit.NamespacedKey
import org.bukkit.entity.HumanEntity
import xyz.alexcrea.cuanvil.anvil.AnvilUseType
import xyz.alexcrea.cuanvil.config.ConfigHolder
import xyz.alexcrea.cuanvil.config.WorkPenaltyType
import xyz.alexcrea.cuanvil.config.WorkPenaltyType.WorkPenaltyPart
import xyz.alexcrea.cuanvil.dependency.DependencyManager
import xyz.alexcrea.cuanvil.dependency.economy.EconomyManager
import xyz.alexcrea.cuanvil.enchant.CAEnchantment
import xyz.alexcrea.cuanvil.util.AnvilUseType
import xyz.alexcrea.cuanvil.util.dialog.AnvilRenameDialogUtil
import java.math.BigDecimal
import java.util.*
/**
@ -21,6 +24,9 @@ object ConfigOptions {
// Path for config values
// ----------------------
const val METRIC_TYPE = "metric_type"
const val METRIC_COLLECT_ERROR = "metric_collect_errors"
const val CAP_ANVIL_COST = "limit_repair_cost"
const val MAX_ANVIL_COST = "limit_repair_value"
const val REMOVE_ANVIL_COST_LIMIT = "remove_repair_limit"
@ -42,6 +48,8 @@ object ConfigOptions {
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"
@ -54,15 +62,25 @@ object ConfigOptions {
const val ENCHANT_COUNT_LIMIT_DEFAULT = "$ENCHANT_COUNT_LIMIT_ROOT.default"
const val ENCHANT_COUNT_LIMIT_ITEMS = "$ENCHANT_COUNT_LIMIT_ROOT.items"
const val DEFAULT_LIMIT_PATH = "default_limit"
const val ENCHANT_LIMIT_ROOT = "enchant_limits"
const val ENCHANT_VALUES_ROOT = "enchant_values"
// Dialog menu rename
const val DIALOG_RENAME_ENABLED = "enable_dialog_rename"
const val DIALOG_MAX_SIZE = "dialog_rename_max_size"
const val DIALOG_RENAME_USE_PERMISSION = "permission_needed_for_dialog_rename"
const val DIALOG_KEEP_USER_TEXT = "dialog_rename_keep_user_text"
// Others
const val DISABLE_MERGE_OVER_ROOT = "disable-merge-over"
const val IMMUTABLE_ENCHANTMENT_LIST = "immutable_enchantments"
// Monetary configs
const val MONETARY_USAGE_ROOT = "monetary_cost"
const val SHOULD_USE_MONEY = "$MONETARY_USAGE_ROOT.enabled"
const val MONEY_CURRENCY = "$MONETARY_USAGE_ROOT.currency"
const val MONETARY_MULTIPLIER_ROOT = "$MONETARY_USAGE_ROOT.multipliers"
// Keys for specific enchantment values
private const val KEY_BOOK = "book"
@ -99,12 +117,23 @@ object ConfigOptions {
const val DEFAULT_PERMISSION_NEEDED_FOR_COLOR = true
const val DEFAULT_USE_OF_COLOR_COST = 0
const val DEFAULT_ENCHANT_LIMIT = 5
const val DEFAULT_PER_COLOR_CODE_PERMISSION = false
// Monetary configs
const val DEFAULT_SHOULD_USE_MONEY = false
const val DEFAULT_MONEY_CURRENCY = "default"
const val DEFAULT_MONEY_MULTIPLIER = 1.0
// Debug flag
private const val DEFAULT_DEBUG_LOG = false
private const val DEFAULT_VERBOSE_DEBUG_LOG = false
// Dialog menu rename
const val DEFAULT_DIALOG_RENAME_ENABLED = false
const val DEFAULT_DIALOG_MAX_SIZE = 256
const val DEFAULT_DIALOG_RENAME_USE_PERMISSION = false
const val DEFAULT_DIALOG_KEEP_USER_TEXT = true
// -------------
// Config Ranges
// -------------
@ -129,9 +158,11 @@ object ConfigOptions {
@JvmField
val USE_OF_COLOR_COST_RANGE = 0..1000
// Valid range for an enchantment limit
@JvmField
val ENCHANT_LIMIT_RANGE = 1..255
val DIALOG_MAX_SIZE_RANGE = 0..Int.MAX_VALUE
// Valid range for an enchantment limit
const val ENCHANT_LIMIT = 255
// Valid range for an enchantment count limit
@JvmField
@ -147,6 +178,11 @@ object ConfigOptions {
// Default max before merge disabled (negative mean enabled)
const val DEFAULT_MAX_BEFORE_MERGE_DISABLED = -1
// -----------
// Permissions
// -----------
private const val RENAME_DIALOG_PERMISSION = "ca.rename.dialog"
// -------------
// Get methods
// -------------
@ -299,6 +335,16 @@ object ConfigOptions {
.getBoolean(PERMISSION_NEEDED_FOR_COLOR, DEFAULT_PERMISSION_NEEDED_FOR_COLOR)
}
/**
* Should each color code require a permission
*/
val usePerColorCodePermission: Boolean
get() {
return ConfigHolder.DEFAULT_CONFIG
.config
.getBoolean(PER_COLOR_CODE_PERMISSION, DEFAULT_PER_COLOR_CODE_PERMISSION)
}
/**
* How many xp should use of color should cost
*/
@ -345,22 +391,12 @@ object ConfigOptions {
return WorkPenaltyPart(penaltyIncrease, penaltyAdditive, exclusivePenaltyIncrease, exclusivePenaltyAdditive)
}
/**
* Default enchantment limit
*/
private val defaultEnchantLimit: Int
get() {
return ConfigHolder.DEFAULT_CONFIG
.config
.getInt(DEFAULT_LIMIT_PATH, DEFAULT_ENCHANT_LIMIT)
}
/**
* Get material enchantment count limit
*
* @return the current enchantment limit. -1 if none
*/
fun getEnchantCountLimit(type: Material): Int? {
fun getEnchantCountLimit(type: NamespacedKey): Int? {
val limit = materialEnchantCountLimit(type)
if(limit != null) return limit
@ -374,8 +410,8 @@ object ConfigOptions {
*
* @return The current enchantment limit. -1 if none
*/
private fun materialEnchantCountLimit(type: Material): Int? {
val path = "$ENCHANT_COUNT_LIMIT_ITEMS.${type.key.key.lowercase()}"
private fun materialEnchantCountLimit(type: NamespacedKey): Int? {
val path = "$ENCHANT_COUNT_LIMIT_ITEMS.${type.key.lowercase()}"
if(!ConfigHolder.DEFAULT_CONFIG.config.isInt(path))
return null
@ -415,46 +451,90 @@ object ConfigOptions {
.getBoolean(VERBOSE_DEBUG_LOGGING, DEFAULT_VERBOSE_DEBUG_LOG)
}
/**
* Is the dialog menu for rename enabled
*/
val doRenameDialog: Boolean
get() {
return ConfigHolder.DEFAULT_CONFIG
.config
.getBoolean(DIALOG_RENAME_ENABLED, DEFAULT_DIALOG_RENAME_ENABLED)
}
/**
* Do the dialog menu require permission
*/
val doRenameDialogUsePermission: Boolean
get() {
return ConfigHolder.DEFAULT_CONFIG
.config
.getBoolean(DIALOG_RENAME_USE_PERMISSION, DEFAULT_DIALOG_RENAME_USE_PERMISSION)
}
fun canUseDialogRename(player: HumanEntity): Boolean {
if(!doRenameDialog || !AnvilRenameDialogUtil.anvilRenameDialog.canSendDialog()) return false
if(doRenameDialogUsePermission && !player.hasPermission(RENAME_DIALOG_PERMISSION)) return false
return true
}
/**
* Do the dialog menu require permission
*/
val renameDialogMaxSize: Int
get() {
return ConfigHolder.DEFAULT_CONFIG
.config
.getInt(DIALOG_MAX_SIZE, DEFAULT_DIALOG_MAX_SIZE)
.takeIf { it in DIALOG_MAX_SIZE_RANGE }
?: Int.MAX_VALUE
}
/**
* Should the text used for rename should be kept in the item's pdc
*/
val shouldKeepRenameText: Boolean
get() {
return ConfigHolder.DEFAULT_CONFIG
.config
.getBoolean(DIALOG_KEEP_USER_TEXT, DEFAULT_DIALOG_KEEP_USER_TEXT)
}
/**
* Get the given [enchantment]'s limit
*/
fun enchantLimit(enchantment: CAEnchantment): Int {
val limit = rawEnchantLimit(enchantment)
if(limit >= 0) return limit.coerceAtMost(ENCHANT_LIMIT)
// get default
return enchantment.defaultMaxLevel()
}
/**
* Get the given [enchantment]'s limit
*/
fun rawEnchantLimit(enchantment: CAEnchantment): Int {
// Test namespace
var limit = enchantLimit(enchantment.key.toString())
if (limit != null) return limit
if (limit >= 0) return limit
// Test legacy (name only)
limit = enchantLimit(enchantment.enchantmentName)
if (limit != null) return limit
if (limit >= 0) return limit
// get default (and test old legacy if present)
return getDefaultLevel(enchantment.enchantmentName)
// Default to negative
return -1
}
/**
* Get the given [enchantmentName]'s limit
*/
private fun enchantLimit(enchantmentName: String): Int? {
private fun enchantLimit(enchantmentName: String): Int {
val path = "${ENCHANT_LIMIT_ROOT}.$enchantmentName"
return CustomAnvil.instance
.config
.getInt(path, ENCHANT_LIMIT_RANGE.first - 1)
.takeIf { it in ENCHANT_LIMIT_RANGE }
}
/**
* Get default value if enchantment do not exist on config
*/
private fun getDefaultLevel(
enchantmentName: String, // compatibility with 1.20.5. TODO better update system
): Int {
if (enchantmentName == "sweeping_edge") {
val limit = enchantLimit("sweeping")
if (limit != null) return limit
}
return defaultEnchantLimit
return CustomAnvil.instance.config
.getInt(path, -1)
}
/**
@ -526,20 +606,20 @@ object ConfigOptions {
fun maxBeforeMergeDisabled(enchantment: CAEnchantment): Int {
val key = enchantment.key.toString()
var value = maxBeforeMergeDisabled(key)
if (value != null) return value
if (value >= 0) return value
// Legacy name
val legacy = enchantment.enchantmentName
value = maxBeforeMergeDisabled(legacy)
if (value != null) return value
if (value >= 0) return value
if (key == "minecraft:sweeping_edge") {
value = maxBeforeMergeDisabled("minecraft:sweeping")
if (value != null) return value
if (value >= 0) return value
// legacy name of legacy enchantment name
value = maxBeforeMergeDisabled("sweeping")
if (value != null) return value
if (value >= 0) return value
}
return DEFAULT_MAX_BEFORE_MERGE_DISABLED
@ -549,14 +629,13 @@ object ConfigOptions {
* Get the given [enchantmentName]'s level before merge is disabled
* a negative value would mean never disabled
*/
private fun maxBeforeMergeDisabled(enchantmentName: String): Int? {
private fun maxBeforeMergeDisabled(enchantmentName: String): Int {
// find if set
val path = "${DISABLE_MERGE_OVER_ROOT}.$enchantmentName"
return CustomAnvil.instance
.config
.getInt(path, ENCHANT_LIMIT_RANGE.min() - 1)
.takeIf { it in ENCHANT_LIMIT_RANGE }
.getInt(path, -1)
}
fun isImmutable(key: NamespacedKey): Boolean {
@ -572,4 +651,29 @@ object ConfigOptions {
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))
}
}

View file

@ -4,9 +4,9 @@ import io.delilaheve.CustomAnvil
import org.bukkit.entity.HumanEntity
import org.bukkit.inventory.ItemStack
import xyz.alexcrea.cuanvil.config.ConfigHolder
import xyz.alexcrea.cuanvil.dependency.DependencyManager
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
@ -31,87 +31,97 @@ object EnchantmentUtil {
) = mutableMapOf<CAEnchantment, Int>().apply {
putAll(this@combineWith)
CustomAnvil.verboseLog("Testing merge")
val bypassFuse = player.hasPermission(CustomAnvil.bypassFusePermission)
val bypassLevel = player.hasPermission(CustomAnvil.bypassLevelPermission)
var maxEnchantCount = ConfigOptions.getEnchantCountLimit(item.type)
var maxEnchantCount = ConfigOptions.getEnchantCountLimit(item.customType)
if(maxEnchantCount == null || maxEnchantCount < 0) maxEnchantCount = Int.MAX_VALUE
other.forEach { (enchantment, level) ->
if(!enchantment.isAllowed(player)) return@forEach
val allowed = other.filter { (enchantment, _) -> enchantment.isAllowed(player) }
val new = allowed.filter{ (enchantment, _) -> !containsKey(enchantment)}
val old = allowed.filter{ (enchantment, _) -> containsKey(enchantment)}
// Get max level or 255 if player can bypass
val maxLevel = if (bypassLevel) { 255 }
fun maxLevel(enchantment: CAEnchantment): Int {
val max = if (bypassLevel) { 255 }
else { ConfigOptions.enchantLimit(enchantment) }
CustomAnvil.verboseLog("Max level of ${enchantment.key} is $maxLevel (bypassLevel is $bypassLevel)")
CustomAnvil.verboseLog("Max level of ${enchantment.key} is $max (bypassLevel is $bypassLevel)")
return max
}
old.forEach { (enchantment, level) ->
// Get max level or 255 if player can bypass
val maxLevel = maxLevel(enchantment)
val cappedLevel = min(level, maxLevel)
// Enchantment not yet in result list
if (!containsKey(enchantment)) {
// 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)
}
val oldLevel = this[enchantment]!! // <- should not be null. (enchantment already in result list)
// ... and they're not the same level
if (oldLevel != cappedLevel) {
// apply the greater of the two or left one if right is above max
this[enchantment] = max(oldLevel, cappedLevel)
}
// Enchantment already in result list
// ... and they're the same level
else {
val oldLevel = this[enchantment]!! // <- should not be null. (enchantment already in result list)
if(bypassFuse){
CustomAnvil.verboseLog("Bypassed conflict check for ${enchantment.key}")
} else {
val conflictType = ConfigHolder.CONFLICT_HOLDER.conflictManager
.isConflicting(this, item, enchantment)
// ... and they are conflicting
if(conflictType != ConflictType.NO_CONFLICT){
// We test if it is allowed to merge at this level
if(!bypassLevel){
val maxBeforeDisabled = ConfigOptions.maxBeforeMergeDisabled(enchantment)
if((maxBeforeDisabled > 0) && (oldLevel >= maxBeforeDisabled)) {
CustomAnvil.verboseLog(
"Enchantment already in result list, and they are conflicting (${enchantment.key}, conflict: $conflictType)")
"Reached max merge before disable for ${enchantment.key}: $oldLevel/$maxBeforeDisabled)")
return@forEach
}
}
// ... and they're not the same level
if (oldLevel != cappedLevel) {
// apply the greater of the two or left one if right is above max
this[enchantment] = max(oldLevel, cappedLevel)
// Now we increase the enchantment level by 1
var newLevel = oldLevel + 1
newLevel = max(min(newLevel, maxLevel), oldLevel)
this[enchantment] = newLevel
}
}
// ... and they're the same level
else {
// We test if it is allowed to merge at this level
if(!bypassLevel){
val maxBeforeDisabled = ConfigOptions.maxBeforeMergeDisabled(enchantment)
if((maxBeforeDisabled > 0) && (oldLevel >= maxBeforeDisabled)) {
CustomAnvil.verboseLog(
"Reached max merge before disable for ${enchantment.key}: $oldLevel/$maxBeforeDisabled)")
return@forEach
}
}
// Now we increase the enchantment level by 1
var newLevel = oldLevel + 1
newLevel = max(min(newLevel, maxLevel), oldLevel)
this[enchantment] = newLevel
if(bypassFuse){
CustomAnvil.verboseLog("Bypassed conflict check for ${enchantment.key}")
} else {
val conflictType = ConfigHolder.CONFLICT_HOLDER.conflictManager
.isConflicting(this, item, enchantment)
// ... and they are conflicting
if(conflictType != ConflictType.NO_CONFLICT){
CustomAnvil.verboseLog(
"Enchantment already in result list, and they are conflicting (${enchantment.key}, conflict: $conflictType)")
this[enchantment] = oldLevel
return@forEach
}
}
}
// Try to add new now
new.forEach { (enchantment, level) ->
// Get max level or 255 if player can bypass
val maxLevel = maxLevel(enchantment)
val cappedLevel = min(level, maxLevel)
// Do not allow new enchantment if above maximum
if(this.size >= maxEnchantCount) return@forEach
// Add the enchantment if it doesn't have conflicts, or if player is allowed to bypass enchantment restrictions
this[enchantment] = cappedLevel
if(bypassFuse){
CustomAnvil.verboseLog("Bypassed conflict check for ${enchantment.key}")
return@forEach
}
val conflictType = ConfigHolder.CONFLICT_HOLDER.conflictManager
.isConflicting(this, item, enchantment)
if (conflictType != ConflictType.NO_CONFLICT) {
CustomAnvil.verboseLog("Enchantment not yet in result list, but there is conflict (${enchantment.key}, conflict: $conflictType)")
this.remove(enchantment)
}
}
}
}

View file

@ -4,6 +4,9 @@ import org.bukkit.Material.ENCHANTED_BOOK
import org.bukkit.inventory.ItemStack
import org.bukkit.inventory.meta.Damageable
import xyz.alexcrea.cuanvil.enchant.CAEnchantment
import xyz.alexcrea.cuanvil.update.UpdateUtils
import xyz.alexcrea.cuanvil.util.MaterialUtil.customType
import xyz.alexcrea.cuanvil.util.MaxDamageCheckerUtil
import kotlin.math.ceil
import kotlin.math.max
import kotlin.math.min
@ -35,6 +38,13 @@ object ItemUtil {
}
private fun maxDamage(damageable: Damageable): Int {
val ver = UpdateUtils.currentMinecraftVersion()
if(ver.major <= 1 && ver.minor <= 20 && ver.patch < 5) return Integer.MAX_VALUE
return MaxDamageCheckerUtil.getMaxDamage(damageable)
}
/**
* Set this [ItemStack]s durability from a combination of the
* [first] and [second] item's durability values
@ -54,7 +64,9 @@ object ItemUtil {
val secondDurability = durability - secondDamage
val combinedDurability = firstDurability + secondDurability
val newDurability = min(combinedDurability, durability)
it.damage = durability - newDurability
val maxDamage = maxDamage(it)
it.damage = min(durability - newDurability, maxDamage)
itemMeta = it
return true
}
@ -90,5 +102,5 @@ object ItemUtil {
*/
fun ItemStack.canMergeWith(
other: ItemStack?
) = (other != null) && (type == other.type || (other.isEnchantedBook()))
) = (other != null) && (customType == other.customType || (other.isEnchantedBook()))
}

View file

@ -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"))
}
}

View file

@ -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<CAEnchantment, Int>,
resultEnchants: MutableMap<CAEnchantment, Int>
): 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
}
}

View file

@ -1,60 +1,60 @@
package xyz.alexcrea.cuanvil.util
package xyz.alexcrea.cuanvil.anvil
import org.bukkit.Material
import xyz.alexcrea.cuanvil.config.WorkPenaltyType.WorkPenaltyPart
import xyz.alexcrea.cuanvil.util.config.LoreEditType
import xyz.alexcrea.cuanvil.config.WorkPenaltyType
import xyz.alexcrea.cuanvil.util.anvil.AnvilUseTypeUtil
enum class AnvilUseType(
val typeName: String, val path: String,
val defaultPenalty: WorkPenaltyPart,
val defaultPenalty: WorkPenaltyType.WorkPenaltyPart,
val displayName: String, val displayMat: Material
) {
RENAME_ONLY(
"rename_only",
WorkPenaltyPart(false, true),
WorkPenaltyType.WorkPenaltyPart(false, true),
"Rename Only", Material.NAME_TAG
),
MERGE(
"merge",
WorkPenaltyPart(true, true),
WorkPenaltyType.WorkPenaltyPart(true, true),
"Merge", Material.ANVIL
),
UNIT_REPAIR(
"unit_repair",
WorkPenaltyPart(true, true),
WorkPenaltyType.WorkPenaltyPart(true, true),
"Unit Repair", Material.DIAMOND
),
CUSTOM_CRAFT(
"custom_craft",
WorkPenaltyPart(false, false),
WorkPenaltyType.WorkPenaltyPart(false, false),
"Custom Craft", Material.CRAFTING_TABLE
),
LORE_EDIT_BOOK_APPEND(
"lore_edit_book_append", "lore_edit.book_and_quil.append",
WorkPenaltyPart(false, false),
WorkPenaltyType.WorkPenaltyPart(false, false),
"Book Add", Material.WRITABLE_BOOK
),
LORE_EDIT_BOOK_REMOVE(
"lore_edit_book_remove", "lore_edit.book_and_quil.remove",
WorkPenaltyPart(false, false),
WorkPenaltyType.WorkPenaltyPart(false, false),
"Book Remove", Material.WRITABLE_BOOK
),
LORE_EDIT_PAPER_APPEND(
"lore_edit_paper_append", "lore_edit.paper.append_line",
WorkPenaltyPart(false, false),
WorkPenaltyType.WorkPenaltyPart(false, false),
"Paper Add", Material.WRITABLE_BOOK
),
LORE_EDIT_PAPER_REMOVE(
"lore_edit_paper_remove", "lore_edit.paper.remove_line",
WorkPenaltyPart(false, false),
WorkPenaltyType.WorkPenaltyPart(false, false),
"Paper Remove", Material.WRITABLE_BOOK
),
;
constructor(
typeName: String,
defaultPenalty: WorkPenaltyPart,
defaultPenalty: WorkPenaltyType.WorkPenaltyPart,
displayName: String, displayMat: Material
) :
this(

View file

@ -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<out String>
): 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<out String>): Boolean
open fun allowed(sender: CommandSender): Boolean {
return true
}
open fun tabCompleter(
sender: CommandSender,
args: Array<out String>,
list: MutableList<String>) {
}
open fun description(): String {
return "no description"
}
}

View file

@ -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<out String>
): Boolean {
// Find sub command to execute based on the provided command name
val subcmd: CASubCommand?
val newargs: Array<out String>
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<out String>
): MutableList<String> {
val result = ArrayList<String>()
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()
}
}

View file

@ -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<out String>): EnumSet<DiagParams> {
val result = EnumSet.noneOf(DiagParams::class.java)
val argSet = HashSet<String>()
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<out String>,
list: MutableList<String>) {
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<out String>
): 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<DiagParams>){
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<Plugin?> = ArrayList<Plugin?>()
val disabledPlugins: MutableList<Plugin?> = ArrayList<Plugin?>()
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<Plugin?> = 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<String, Int>()
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()}")
}
}

View file

@ -2,17 +2,21 @@ 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<out String>): Boolean {
if (!sender.hasPermission(CustomAnvil.editConfigPermission)) {
override fun executeCommand(sender: CommandSender,
cmd: Command,
cmdstr: String,
args: Array<out String>): Boolean {
if (sender !is HumanEntity) return false
if (!allowed(sender)) {
sender.sendMessage(GuiGlobalActions.NO_EDIT_PERM)
return false
}
@ -25,10 +29,17 @@ class EditConfigExecutor : CommandExecutor {
return false
}
if (sender !is HumanEntity) return false
MainConfigGui.getInstance().show(sender)
return true
}
override fun allowed(sender: CommandSender): Boolean {
return sender.hasPermission(CustomAnvil.editConfigPermission)
}
override fun description(): String {
return "Gui to edit the plugin's config"
}
}

View file

@ -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<String, CASubCommand>
override fun executeCommand(sender: CommandSender,
cmd: Command,
cmdstr: String,
args: Array<out String>): 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"
}
}

View file

@ -11,9 +11,13 @@ 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<out String>): Boolean {
if (!sender.hasPermission(CustomAnvil.commandReloadPermission)) {
class ReloadExecutor : CASubCommand() {
override fun executeCommand(sender: CommandSender,
cmd: Command,
cmdstr: String,
args: Array<out String>): Boolean {
if (!allowed(sender)) {
sender.sendMessage("§cYou do not have permission to reload the config")
return false
}
@ -31,6 +35,14 @@ class ReloadExecutor : CommandExecutor {
return commandSuccess
}
override fun allowed(sender: CommandSender): Boolean {
return sender.hasPermission(CustomAnvil.commandReloadPermission)
}
override fun description(): String {
return "Reload the configuration of this plugin"
}
/**
* Execute the command, return true if success or false otherwise
*/

View file

@ -1,23 +1,28 @@
package xyz.alexcrea.cuanvil.dependency
import com.willfp.eco.core.gui.player
import com.maddoxh.superEnchants.SuperEnchants
import io.delilaheve.CustomAnvil
import net.kyori.adventure.text.Component
import org.bukkit.Bukkit
import org.bukkit.ChatColor
import org.bukkit.command.CommandSender
import org.bukkit.entity.HumanEntity
import org.bukkit.entity.Player
import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.event.inventory.PrepareAnvilEvent
import org.bukkit.inventory.AnvilInventory
import org.bukkit.inventory.Inventory
import org.bukkit.inventory.InventoryView
import org.bukkit.inventory.ItemStack
import xyz.alexcrea.cuanvil.anvil.AnvilCost
import xyz.alexcrea.cuanvil.anvil.AnvilUseType
import xyz.alexcrea.cuanvil.api.event.listener.CAClickResultBypassEvent
import xyz.alexcrea.cuanvil.api.event.listener.CAEarlyPreAnvilBypassEvent
import xyz.alexcrea.cuanvil.api.event.listener.CAPreAnvilBypassEvent
import xyz.alexcrea.cuanvil.api.event.listener.CATreatAnvilResultEvent
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.ExternGuiTester
import xyz.alexcrea.cuanvil.dependency.gui.GuiTesterSelector
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.*
@ -27,14 +32,14 @@ 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.AnvilUseType
import xyz.alexcrea.cuanvil.util.MetricsUtil.trackError
import java.util.logging.Level
object DependencyManager {
lateinit var scheduler: TaskScheduler
lateinit var packetManager: PacketManager
var externGuiTester: ExternGuiTester? = null
var externGuiTester: GenericExternGuiTester = GenericExternGuiTester()
var enchantmentSquaredCompatibility: EnchantmentSquaredDependency? = null
var ecoEnchantCompatibility: EcoEnchantDependency? = null
@ -45,6 +50,8 @@ object DependencyManager {
var axPlayerWarpsCompatibility: AxPlayerWarpsDependency? = null
var itemsAdderCompatibility: ItemsAdderDependency? = null
val genericDependencies = ArrayList<GenericPluginDependency>()
fun loadDependency() {
@ -60,7 +67,6 @@ object DependencyManager {
// Packet Manager
val forceProtocolib = ConfigHolder.DEFAULT_CONFIG.config.getBoolean("force_protocolib", false)
packetManager = PacketManagerSelector.selectPacketManager(forceProtocolib)
externGuiTester = GuiTesterSelector.selectGuiTester
// Enchantment Squared dependency
if (pluginManager.isPluginEnabled("EnchantsSquared")) {
@ -97,6 +103,12 @@ object DependencyManager {
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")!!))
@ -104,6 +116,12 @@ object DependencyManager {
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()
@ -131,26 +149,35 @@ object DependencyManager {
ecoEnchantCompatibility?.handleConfigReload()
}
private fun logException(target: CommandSender, e: Exception) {
CustomAnvil.instance.logger.log(
Level.SEVERE,
"Error while trying to handle custom anvil supported plugin: ",
e
)
trackError(e)
// Finally, warn the player
target.sendMessage(
"[" + ChatColor.YELLOW.toString() + "CustomAnvil" + ChatColor.WHITE.toString() + "] " +
ChatColor.RED.toString() + "Error while handling the anvil."
)
}
private fun logExceptionAndClear(target: CommandSender, inventory: Inventory, e: Exception) {
// Just in case to avoid illegal items
inventory.setItem(ANVIL_OUTPUT_SLOT, null)
logException(target, e)
}
// Return true if should bypass (either by a dependency or error)
// called before immutability test
fun earlyTryEventPreAnvilBypass(event: PrepareAnvilEvent, player: HumanEntity): Boolean {
try {
return earlyUnsafeTryEventPreAnvilBypass(event, player)
} catch (e: Exception) {
CustomAnvil.instance.logger.log(
Level.SEVERE,
"Error while trying to handle custom anvil supported plugin: ",
e
)
// Just in case to avoid illegal items
event.inventory.setItem(ANVIL_OUTPUT_SLOT, null)
// Finally, warn the player, maybe a lot of time but better warn than do nothing
event.view.player.sendMessage(
"[" + ChatColor.YELLOW.toString() + "CustomAnvil" + ChatColor.WHITE.toString() + "] " +
ChatColor.RED.toString() + "Error while handling the anvil."
)
logExceptionAndClear(event.view.player, event.inventory, e)
return true
}
}
@ -163,7 +190,7 @@ object DependencyManager {
var bypass = bypassEvent.isCancelled
// Test if the inventory is a gui(version specific)
if (!bypass && (externGuiTester?.testIfGui(event.view) == true)) bypass = true
if (!bypass && externGuiTester.testIfGui(event.view)) bypass = true
// Test if in an ax player warp rating gui
if (!bypass && (axPlayerWarpsCompatibility?.testIfGui(player) == true)) bypass = true
@ -172,29 +199,16 @@ object DependencyManager {
}
// Return true if should bypass (either by a dependency or error)
fun tryEventPreAnvilBypass(event: PrepareAnvilEvent, player: HumanEntity): Boolean {
fun tryEventPreAnvilBypass(event: PrepareAnvilEvent, player: Player): Boolean {
try {
return unsafeTryEventPreAnvilBypass(event, player)
} catch (e: Exception) {
CustomAnvil.instance.logger.log(
Level.SEVERE,
"Error while trying to handle custom anvil supported plugin: ",
e
)
// Just in case to avoid illegal items
event.inventory.setItem(ANVIL_OUTPUT_SLOT, null)
// Finally, warn the player, maybe a lot of time but better warn than do nothing
event.view.player.sendMessage(
"[" + ChatColor.YELLOW.toString() + "CustomAnvil" + ChatColor.WHITE.toString() + "] " +
ChatColor.RED.toString() + "Error while handling the anvil."
)
logExceptionAndClear(event.view.player, event.inventory, e)
return true
}
}
private fun unsafeTryEventPreAnvilBypass(event: PrepareAnvilEvent, player: HumanEntity): Boolean {
private fun unsafeTryEventPreAnvilBypass(event: PrepareAnvilEvent, player: Player): Boolean {
// Run the event
val bypassEvent = CAPreAnvilBypassEvent(event)
Bukkit.getPluginManager().callEvent(bypassEvent)
@ -219,35 +233,24 @@ object DependencyManager {
// Return null if there was an issue
fun tryTreatAnvilResult(
event: PrepareAnvilEvent,
view: InventoryView,
inventory: Inventory, // TODO REMOVE, use view instead on legacy removal
player: HumanEntity,
result: ItemStack,
useType: AnvilUseType,
cost: Int
): CATreatAnvilResultEvent? {
val treatEvent = CATreatAnvilResultEvent(event, useType, result, cost)
cost: AnvilCost
): ItemStack? {
val treatEvent = CATreatAnvilResult2Event(view, inventory, useType, result, cost)
try {
unsafeTryTreatAnvilResult(treatEvent)
return treatEvent;
return treatEvent.result
} catch (e: Exception) {
CustomAnvil.instance.logger.log(
Level.SEVERE,
"Error while trying to handle custom anvil supported plugin: ",
e
)
// Just in case to avoid illegal items
event.inventory.setItem(ANVIL_OUTPUT_SLOT, null)
// Finally, warn the player, maybe a lot of time but better warn than do nothing
event.view.player.sendMessage(
"[" + ChatColor.YELLOW.toString() + "CustomAnvil" + ChatColor.WHITE.toString() + "] " +
ChatColor.RED.toString() + "Error while handling the anvil."
)
logExceptionAndClear(player, inventory, e)
return null
}
}
private fun unsafeTryTreatAnvilResult(event: CATreatAnvilResultEvent) {
private fun unsafeTryTreatAnvilResult(event: CATreatAnvilResult2Event) {
Bukkit.getPluginManager().callEvent(event)
excellentEnchantsCompatibility?.treatAnvilResult(event)
@ -258,20 +261,7 @@ object DependencyManager {
try {
return unsafeTryClickAnvilResultBypass(event, inventory)
} catch (e: Exception) {
CustomAnvil.instance.logger.log(
Level.SEVERE,
"Error while trying to handle custom anvil supported plugin: ",
e
)
// Just in case to avoid illegal items
event.inventory.setItem(ANVIL_OUTPUT_SLOT, null)
// Finally, warn the player, maybe a lot of time but better warn than do nothing
event.whoClicked.sendMessage(
"[" + ChatColor.YELLOW.toString() + "CustomAnvil" + ChatColor.WHITE.toString() + "] " +
ChatColor.RED.toString() + "Error while handling the anvil."
)
logExceptionAndClear(event.view.player, event.inventory, e)
return true
}
}
@ -297,14 +287,31 @@ object DependencyManager {
}
// Test if the inventory is a gui(version specific)
if (!bypass && (externGuiTester?.testIfGui(event.view) == true)) bypass = true
if (!bypass && externGuiTester.testIfGui(event.view)) bypass = true
// Test if in an ax player warp rating gui
if (!bypass && (axPlayerWarpsCompatibility?.testIfGui(event.player) == true)) bypass = true
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<Component?> {
val dummy = item.clone()

View file

@ -6,29 +6,29 @@ object MinecraftVersionUtil {
val craftbukkitVersion: String?
get() {
val versionParts = UpdateUtils.currentMinecraftVersionArray()
if (versionParts[0] != 1) return null
val version = UpdateUtils.currentMinecraftVersion()
if (version.major != 1) return null
return when (versionParts[1]) {
17 -> when (versionParts[2]) {
return when (version.minor) {
17 -> when (version.patch) {
0, 1 -> "1_17R1"
else -> null
}
18 -> when (versionParts[2]) {
18 -> when (version.patch) {
0, 1 -> "1_18R1"
2 -> "1_18R2"
else -> null
}
19 -> when (versionParts[2]) {
19 -> when (version.patch) {
0, 1, 2 -> "1_19R1"
3 -> "1_19R2"
4 -> "1_19R3"
else -> null
}
20 -> when (versionParts[2]) {
20 -> when (version.patch) {
0, 1 -> "1_20R1"
2 -> "1_20R2"
3, 4 -> "1_20R3"
@ -36,7 +36,7 @@ object MinecraftVersionUtil {
else -> null
}
21 -> when (versionParts[2]) {
21 -> when (version.patch) {
0, 1 -> "1_21R1"
2, 3 -> "1_21R2"
4 -> "1_21R3"
@ -51,4 +51,8 @@ object MinecraftVersionUtil {
}
}
val isTooNewForSpigot: Boolean get() {
return UpdateUtils.currentMinecraftVersion().major != 1
}
}

View file

@ -17,7 +17,7 @@ import xyz.alexcrea.cuanvil.update.Version
import java.io.InputStreamReader
object DataPackDependency {
private val START_DETECT_VERSION = Version(1, 19, 0)
private val START_DETECT_VERSION = Version(1, 20, 5)
/**
* Map of the latest CustomAnvil update related to the pack
@ -145,7 +145,7 @@ object DataPackDependency {
CustomAnvil.instance.logger.warning("Could not find material $name for item group $groupName")
continue
}
group.addToPolicy(mat)
group.addToPolicy(mat.key)
}
for (name in section.getStringList("groups")) {
val otherGroup = MaterialGroupApi.getGroup(name)

View file

@ -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;
}

View file

@ -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())
}
}

Some files were not shown because too many files have changed in this diff Show more