Compare commits

..

138 Commits

Author SHA1 Message Date
Auxilor
ac10fa46dc Fixed javadoc 2023-01-23 11:27:44 +00:00
Auxilor
7c616e64ae Fixed XP / XPL 2023-01-18 20:03:28 +00:00
Auxilor
708f9130c6 Removed old docs URL 2023-01-18 12:53:13 +00:00
Auxilor
9118d49c67 Updated README.md 2023-01-18 12:52:29 +00:00
Auxilor
a1ce72476f Fixed README.md 2023-01-18 12:38:39 +00:00
Auxilor
2cfab99644 Fixed a plethora of codestyle issues 2023-01-17 17:57:40 +00:00
Auxilor
cc9b3f7710 Small blips 2023-01-17 17:50:07 +00:00
Auxilor
5bfe48c8d9 Updated CONTRIBUTING.md 2023-01-17 17:48:45 +00:00
Auxilor
22ff157ffc Merge branch 'MisterFrans_master' into develop 2023-01-17 17:44:35 +00:00
Auxilor
720dbe789c Merge branch 'patch-1' into develop 2023-01-17 17:42:49 +00:00
Auxilor
b51dd51941 Deprecated Prerequisite#HAS_BUNGEECORD and Prerequisite#HAS_VELOCITY 2023-01-17 16:05:06 +00:00
Auxilor
f3ffaa4cf6 Javadoc improvements 2023-01-17 16:02:07 +00:00
Auxilor
085032e315 Further command cleanup, added lang.yml validation and removed magic strings 2023-01-17 15:59:15 +00:00
Auxilor
3d920ee2b4 Updated to 6.49.0 2023-01-17 12:20:51 +00:00
Auxilor
06a04e4375 Fixed spelling inconsistency 2023-01-17 12:19:59 +00:00
Auxilor
7349f15784 Cleaned up command rework 2023-01-17 12:18:10 +00:00
Auxilor
a4c77857d5 Merge branch 'master' into Samkist_master 2023-01-17 12:00:10 +00:00
Auxilor
999fafc8df Javadoc cleanjp 2023-01-17 12:00:04 +00:00
Auxilor
0d533850f6 Updated to 6.48.3 2023-01-13 17:53:44 +00:00
Auxilor
569f9cfcb4 Merge remote-tracking branch 'origin/master' 2023-01-13 17:52:53 +00:00
Auxilor
f0619f2374 Updated to 6.48.2 2023-01-13 17:52:48 +00:00
Auxilor
7e8d97e11d Updated MythicMobs and ExecutableItems 2023-01-13 17:52:20 +00:00
Auxilor
d3414f25ad Cleanup 2023-01-13 17:39:29 +00:00
MCCasper
f0cf118448 Update McmmoManager.java
Change to not quit after first iteration of loop
2022-12-29 10:07:05 -05:00
François
297bb10b85 Removal of an unnecessary semicolon 2022-12-27 13:06:22 +01:00
François
751624bc8d Update of Lands api to the latest version (6.26.7). 2022-12-26 15:34:11 +01:00
Auxilor
8b1b15a3e4 Updated to 6.48.2 2022-12-20 15:05:30 +00:00
Auxilor
7fe330bafb Fixed 1.19.3 support 2022-12-20 15:05:21 +00:00
Auxilor
20584b2a9b Updated to 6.48.1 2022-12-12 12:13:32 +00:00
Auxilor
bd7594a117 Fixed 1.19.3 build 2022-12-12 12:13:22 +00:00
Samuel Pizette
f1bfa21270 restructured some methods 2022-12-11 20:32:23 -05:00
Samuel Pizette
01aa1e708a more missing documentations 2022-12-11 14:17:16 -05:00
Samuel Pizette
8424baa285 wrote missing documentations 2022-12-11 14:14:37 -05:00
Auxilor
d9a8d26990 Fixed 1.19.3 build 2022-12-11 17:07:33 +00:00
Auxilor
4d3eeaaefc Added 1.19.3 support 2022-12-11 16:58:58 +00:00
Samuel Pizette
d54a2b9516 fixed method signature of extensions 2022-12-10 17:18:06 -05:00
Samuel Pizette
f7f12b6255 finished working refactor 2022-12-10 16:50:52 -05:00
Samuel Pizette
42eb1344a6 removed unneeded debug statement 2022-12-10 16:08:33 -05:00
Samuel Pizette
4c2a8585cc got command functionality working 2022-12-10 16:08:01 -05:00
Samuel Pizette
8cccc67b0d wrote documentation for Eco Plugin and Eco Sub Command 2022-12-09 14:42:12 -05:00
Samuel Pizette
396d74497c wrote EcoHandledCommand.kt documentation 2022-12-09 14:38:13 -05:00
Samuel Pizette
49602dce04 rewrote notify documentation 2022-12-09 14:24:13 -05:00
Samuel Pizette
5da811ba74 added notifyPermissionRequired and updated some comments 2022-12-09 13:52:03 -05:00
Samuel Pizette
a4d57e21fe cleaned up eco handled command 2022-12-09 13:29:33 -05:00
Samuel Pizette
4b8efdc79f whoops 2022-12-09 12:10:39 -05:00
Auxilor
610110efde Improved CombinedDisplayPrice 2022-12-07 16:38:32 +00:00
Samuel Pizette
a87f675269 updated method signature annotations to accurately reflect behavior 2022-12-06 03:42:34 -05:00
Samuel Pizette
a371d314b8 shortened logic of notifyNull extension 2022-12-06 03:38:28 -05:00
Samuel Pizette
9a9097adc5 updated some annotations 2022-12-05 20:27:31 -05:00
Samuel Pizette
0669a57e4b updated some annotations 2022-12-05 20:08:35 -05:00
Samuel Pizette
692eaf6836 changed onTabComplete return signature in EcoDelegatedBukkitCommand.kt from nullable to not null 2022-12-05 19:22:09 -05:00
Auxilor
7f9052c64d Updated to 6.48.0 2022-12-05 12:34:53 +00:00
Auxilor
55a841b3f5 Added CombinedDisplayPrice 2022-12-05 12:34:45 +00:00
Samuel Pizette
85f02c5ca2 wrote notification kotlin extensions 2022-12-04 19:35:54 -05:00
Samuel Pizette
74c428b90d rearranged notify methods 2022-12-04 19:17:36 -05:00
Samuel Pizette
fd8c67fa66 wrote handle tab complete for ecohandled command 2022-12-04 19:13:23 -05:00
Samuel Pizette
a396754e2e commands rework progress
- Wrote EcoDelegatedBukkitCommand.kt
- Wrote EcoHandledCommand.kt
- Wrote Eco#createSubCommand
- Renamed RegistrableCommandBase.java to PluginCommandBase
- Moved most of EcoPluginCommand.kt to EcoHandledCommand.kt
- Changed Delegate type in PluginCommand from CommandBase to PluginCommandBase
2022-12-04 18:52:19 -05:00
Samuel Pizette
6f97f47712 removed getAliases and getDescription from commandbase 2022-12-04 18:50:03 -05:00
Samuel Pizette
d1109e485a more fixing auto reformat 2022-12-04 17:56:07 -05:00
Samuel Pizette
476e5c7cae fuck auto reformat 2022-12-04 17:54:50 -05:00
Samuel Pizette
dc2b7a6fda returned instance get to original location 2022-12-04 17:53:49 -05:00
Samuel Pizette
00f18519b0 progress commit 2022-12-04 17:52:05 -05:00
Samuel Pizette
f7ea5fd182 fix eco format 2022-11-30 16:50:02 -05:00
Samuel Pizette
70d29c872a removed HandledCommand 2022-11-29 16:29:23 -05:00
Samuel Pizette
f79f4a84c3 deprecated DelegatedBukkitCommand 2022-11-29 16:29:16 -05:00
Samuel Pizette
9af63907ef Merge branch 'master' into master 2022-11-29 16:28:37 -05:00
Samuel Pizette
c9aa92895b work in progress commit 2022-11-29 16:13:01 -05:00
Auxilor
c57c824027 Cleaned up UltraEconomy integration 2022-11-29 15:56:30 +00:00
Auxilor
7cb905e65a Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	eco-api/src/main/java/com/willfp/eco/core/gui/slot/ConfigSlot.java
2022-11-29 15:47:52 +00:00
Auxilor
31a2c7e338 Added lore to ConfigSlot 2022-11-29 15:47:41 +00:00
Auxilor
1759b52f82 Added lore to ConfigSlot 2022-11-29 15:37:36 +00:00
Auxilor
ccf93e3a4d Fixed 2x2 crafting bug 2022-11-28 23:55:45 +00:00
Auxilor
abd07389ab Fixed dynamic command registration 2022-11-28 17:28:29 +00:00
Auxilor
80ad738bb2 Removed AbstractItemStackBuilder correctifying things 2022-11-28 15:10:28 +00:00
Auxilor
b01105819a Updated EconomyShopGUI and ShopGUI+ integrations 2022-11-28 14:46:38 +00:00
Auxilor
a7c08b0731 Updated shop API (again) 2022-11-28 14:40:42 +00:00
Auxilor
7e4c071698 Fixed javadoc 2022-11-28 14:28:38 +00:00
Auxilor
f94f7ead08 Reworked price/shop API to have call-site multipliers 2022-11-28 14:26:46 +00:00
Auxilor
b21c5bf3a9 Fixed captive filter and GUI drag bug 2022-11-28 13:57:41 +00:00
Auxilor
7a9e8c5c10 Fixed bug with multiple stacked paginated menus 2022-11-25 16:51:17 +00:00
Auxilor
a6ddbc46ab Fixes 2022-11-25 16:28:24 +00:00
Auxilor
ffaee137d8 Overhauled ShopSellEvent API 2022-11-24 23:19:03 +00:00
Auxilor
18d882dac6 Reworked Shop API 2022-11-24 23:12:25 +00:00
Auxilor
52841f7f04 Fixed bug with price copying 2022-11-23 22:31:37 +00:00
Auxilor
47b72e9243 Javadoc fix 2022-11-23 22:03:44 +00:00
Auxilor
854a10e8fd Added Price#withMultiplier 2022-11-23 22:00:07 +00:00
Auxilor
823ef6477b Refactoring for naming, updated to 6.47.0 2022-11-23 21:56:07 +00:00
Auxilor
eccb146852 Reworked slot changes into canCaptivateItem 2022-11-18 18:08:21 +00:00
Auxilor
d877b707d6 Improved lang.yml/config.yml warning messages 2022-11-17 15:12:28 +00:00
Auxilor
bcb7401c74 Fixed captive slot changes PR 2022-11-17 15:10:05 +00:00
Auxilor
f05c5f3cd6 Fixed UltraEconomy integration 2022-11-17 15:02:40 +00:00
_OfTeN_
3bd8bccb81 Added UltraEconomy support to Price Lookup 2022-11-17 02:58:53 +03:00
_OfTeN_
6f55787c84 Added ability to filter items for captive slots 2022-11-13 02:50:23 +03:00
_OfTeN_
eb4dc168fc Added proper error displaying when missing config or lang ymls 2022-11-11 02:08:19 +03:00
Auxilor
c5085de5d1 Updated to 6.46.1 2022-11-09 12:59:44 +00:00
Auxilor
019cdbf9c8 Fixed tab completion bug 2022-11-09 12:59:37 +00:00
Auxilor
d5ddcaea4b Cleaned up Menu API 2022-11-08 18:11:27 +00:00
Auxilor
d3a7ef72e8 Thread naming 2022-11-08 16:39:28 +00:00
Auxilor
a311ce1227 Added save-interval option 2022-11-08 16:01:46 +00:00
Auxilor
5c0d4540a8 Merge branch 'pvpmanager-support' into develop 2022-11-08 15:55:03 +00:00
Auxilor
7e66ee8071 Merge branch 'fix/exposed' into develop 2022-11-08 15:54:06 +00:00
Auxilor
fd233df736 Added relevant kt extension for onBuild 2022-11-07 16:43:32 +00:00
Auxilor
6baf636e6a Added MenuBuilder#onBuild 2022-11-07 16:42:40 +00:00
Auxilor
9ee579f2c4 Updated to 6.46.0 2022-11-07 16:40:14 +00:00
Auxilor
3b5ea87353 Fixes to ReactiveSlot 2022-11-07 15:42:46 +00:00
Auxilor
00d32ed218 Added CaptiveItemChangeEvent 2022-11-07 15:32:01 +00:00
Auxilor
5ce9a1c04e Added ItemStack#modify and TestableItem#modify 2022-11-07 15:13:19 +00:00
Auxilor
966549065d Added PlayableSound 2022-11-07 15:08:26 +00:00
Auxilor
ee2911b57c Revert "Added "undyable" arg parser"
This reverts commit a9c32000d8.
2022-11-07 15:00:47 +00:00
_OfTeN_
f15ec5eec0 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	eco-core/core-plugin/src/main/kotlin/com/willfp/eco/internal/spigot/integrations/customitems/CustomItemsScyther.kt
2022-11-07 15:54:25 +03:00
_OfTeN_
a9c32000d8 Added "undyable" arg parser 2022-11-07 15:54:02 +03:00
_OfTeN_
b68459951f Fixed scyther integration 2022-11-07 14:51:03 +03:00
Will FP
b520c76169 Update README.md 2022-11-06 20:03:25 +00:00
Auxilor
d081afbd8e Improved command sync 2022-11-06 19:23:11 +00:00
Auxilor
46fd0439a5 Fixed dynamic command reg 2022-11-06 19:17:21 +00:00
Auxilor
ad58ce4a74 GUI Fixes + Improvements 2022-11-06 14:21:49 +00:00
Auxilor
b5cd8f42e0 Fixed illusioner goals 2022-11-05 21:25:52 +00:00
Auxilor
d0baf50709 Fixed illusioner goals 2022-11-04 15:07:34 +00:00
Auxilor
2496f318fa Removed health-fixer 2022-11-04 13:57:16 +00:00
Auxilor
048c200c95 Changed ConfiguredPrice to be a delegate 2022-11-01 22:05:25 +00:00
Auxilor
17446acb2e Fixed javadoc 2022-11-01 21:55:56 +00:00
Auxilor
7929113c91 Improved PriceItem 2022-11-01 21:53:27 +00:00
Auxilor
6acc5864bd Reworked Price API (again) 2022-11-01 21:36:09 +00:00
Kees Monshouwer
fa64950d28 add support for PvPManager 2022-10-31 09:39:17 +01:00
Auxilor
730c20dbc0 Added Testable Extensions 2022-10-30 16:53:29 +00:00
Auxilor
0c5ae54c3a Codestyle 2022-10-30 16:34:08 +00:00
Auxilor
b48e80837d Fixed javadoc 2022-10-30 16:32:30 +00:00
Auxilor
17eb4cf5f8 Added alias and description support to PluginCommand 2022-10-30 16:02:44 +00:00
Auxilor
890f85fa56 Added Player versions of onExecute and tabComplete 2022-10-30 15:58:14 +00:00
Auxilor
86b427c95e Improved commands 2022-10-30 15:21:21 +00:00
Auxilor
8c1fde57b0 Improved DelegatedBukkitCommand 2022-10-30 15:03:17 +00:00
Auxilor
2efc040a8e Added dynamic command registration 2022-10-29 19:06:15 +01:00
Auxilor
54b2b42512 Added price multipliers 2022-10-29 16:53:24 +01:00
Auxilor
e67d9d634c Fixed javadoc 2022-10-29 16:34:18 +01:00
Auxilor
39fb676b9a Updated to 6.45.0 2022-10-29 16:28:57 +01:00
Auxilor
ec8936b765 Updated price API 2022-10-29 16:28:48 +01:00
DaRacci
052cd74756 fix: explicit database for transactions 2022-10-28 01:06:35 +11:00
_OfTeN_
7767e48e51 Fixed scyther integration 2022-09-14 23:03:20 +03:00
136 changed files with 3620 additions and 1097 deletions

View File

@@ -1,38 +1,7 @@
# How to contribute to eco
## Codestyle
Please open any Pull Requests into the `develop` branch or ideally into a new branch for your changes. PRs that go into `master` won't be ignored, but I have to checkout and merge manually, which makes your PR show as being closed.
1. The eco checkstyle is in /config/checkstyle.xml
Do not write any Kotlin-only APIs; all API components should be written in Java, Kotlin extensions should not have functionality that isn't available in java. The same applies the other way round, do not write any backend code in Java, it should be Kotlin-exclusive.
- The pull request must not have any checkstyle issues.
- Every method and field must have a javadoc attached.
2. Use JetBrains annotations
- Every parameter should be annotated with @NotNull or @Nullable
3. Imports
- No group (*) imports.
- No static imports.
4. Kotlin
- Kotlin should be the only language used in the backend, java should be the only language used in the frontend.
- Kotlin API extensions should only be for creating extension functions and extra niceties that aren't possible in java.
Do not write API components in kotlin.
- Kotlin code should never be called directly from the frontend Java API. Kotlin API extensions should always rely on
java, not the other way round.
## Dependency Injection
- eco uses Dependency Injection
- Any calls to Eco#getHandler#getEcoPlugin are code smells and should never be used unless **absolutely necessary**.
- NamespacedKeys, FixedMetadataValues, Runnables, and Schedules should be managed using AbstractEcoPlugin through DI.
- Any DI class should extend PluginDependent where possible. If the class extends another, then you **must** store the
plugin instance in a private final variable called **plugin** with a private or protected getter.
## Other
- All drops **must** be sent through a DropQueue - calls to World#dropItem will get your PR rejected.
- eco is built with java 17.
If you have any questions about contributing, feel free to ask in the [Discord](https://discord.gg/ZcwpSsE)!

View File

@@ -1,5 +1,5 @@
# eco
eco is a powerful Spigot development library that simplifies the process of plugin creation and supercharges
eco is a powerful Spigot plugin framework that simplifies the process of plugin creation and supercharges
your plugins.
It's the engine behind [EcoEnchants](https://polymart.org/resource/490), [Reforges](https://polymart.org/resource/1330),
[EcoItems](https://polymart.org/resource/1247), [EcoSkills](https://polymart.org/resource/1351),
@@ -16,30 +16,54 @@ and many more.
<a href="https://bstats.org/plugin/bukkit/EcoEnchants" alt="bstats players">
<img src="https://img.shields.io/bstats/players/7666?color=informational"/>
</a>
<a href="https://plugins.auxilor.io/" alt="Docs (gitbook)">
<img src="https://img.shields.io/badge/docs-gitbook-informational"/>
</a>
<a href="https://discord.gg/ZcwpSsE/" alt="Discord">
<img src="https://img.shields.io/discord/452518336627081236?label=discord&color=informational"/>
</a>
<a href="https://github.com/Auxilor/eco/actions/workflows/java-ci.yml" alt="Latest Dev Build">
<img src="https://img.shields.io/github/workflow/status/Auxilor/eco/Java%20CI/develop?color=informational"/>
<img src="https://img.shields.io/github/actions/workflow/status/Auxilor/eco/java-ci.yml?branch=develop&color=informational"/>
</a>
</p>
eco comes packed with all the tools you need in your plugins:
- Modern command API
- Native color parsing with full hex/RGB/MiniMessage support
- Yaml/JSON/TOML config system
- Persistent data storage API with Yaml/MySQL/MongoDB support
- Packet item display system
- Entity AI API with near-1:1 NMS mappings
- More events
- Extension API, essentially plugins for plugins
- Fluent dependency injection for NamespacedKey, Metadata values, etc.
- Ultra-fast ItemStack reimplementation bypassing ItemMeta
- Complete GUI API with pre-made components available from [ecomponent](https://github.com/Auxilor/ecomponent)
- Over 30 native integrations for other plugins
- First-class custom item support with lookup strings
- Math expression parsing via [Crunch](https://github.com/Redempt/Crunch)
- Particle lookups
- Complete Placeholder API
- Price system, supporting economy plugins, XP, Items, etc.
- NMS/Version-specific tooling
- Custom crafting recipe API with support for stacks and custom items
- Native plugin update checking
- Native bStats support
- Full Kotlin support and native extensions
- Tooling to make meta-frameworks, like [libreforge](https://github.com/Auxilor/libreforge)
- And much more
# For server owners
- Requires ProtocolLib to be installed: get the latest version [here](https://www.spigotmc.org/resources/protocollib.1997/)
- Supports 1.17+
## Downloads
- Stable (Recommended): [GitHub](https://github.com/Auxilor/eco/releases), [Polymart](https://polymart.org/resource/eco.773)
- Dev (Not Recommended): [GitHub](https://github.com/Auxilor/eco/actions/workflows/java-ci.yml) (Open latest run and download)
- Stable: [GitHub](https://github.com/Auxilor/eco/releases), [Polymart](https://polymart.org/resource/eco.773)
- Dev: [GitHub](https://github.com/Auxilor/eco/actions/workflows/java-ci.yml) (Open latest run and download)
# For developers
## Javadoc
The 6.38.2 Javadoc can be found [here](https://javadoc.jitpack.io/com/willfp/eco/6.38.2/javadoc/)
The 6.49.0 Javadoc can be found [here](https://javadoc.jitpack.io/com/willfp/eco/6.49.0/javadoc/)
## Plugin Information
@@ -68,7 +92,7 @@ dependencies {
}
```
Replace `Tag` with a release tag for eco, eg `6.27.2`.
Replace `Tag` with a release tag for eco, eg `6.49.0`.
Maven:
@@ -88,7 +112,7 @@ Maven:
</dependency>
```
Replace `Tag` with a release tag for eco, eg `6.27.2`.
Replace `Tag` with a release tag for eco, eg `6.49.0`.
## Build locally:
@@ -103,7 +127,7 @@ cd eco
## License
*Click here to read [the entire license](https://github.com/Auxilor/eco/blob/master/LICENSE.md).*
eco is licensed under GNU GPL3. *Click here to read [the entire license](https://github.com/Auxilor/eco/blob/master/LICENSE.md).*
<h1 align="center">
Check out our partners!

View File

@@ -25,6 +25,7 @@ dependencies {
implementation(project(path = ":eco-core:core-nms:v1_18_R1", configuration = "reobf"))
implementation(project(path = ":eco-core:core-nms:v1_18_R2", configuration = "reobf"))
implementation(project(path = ":eco-core:core-nms:v1_19_R1", configuration = "reobf"))
implementation(project(path = ":eco-core:core-nms:v1_19_R2", configuration = "reobf"))
}
allprojects {
@@ -77,6 +78,8 @@ allprojects {
// LibsDisguises
maven("https://repo.md-5.net/content/groups/public/")
maven("https://repo.techscode.com/repository/maven-releases/")
}
dependencies {

View File

@@ -1,5 +1,8 @@
package com.willfp.eco.core;
import com.willfp.eco.core.command.CommandBase;
import com.willfp.eco.core.command.PluginCommandBase;
import com.willfp.eco.core.command.impl.PluginCommand;
import com.willfp.eco.core.config.ConfigType;
import com.willfp.eco.core.config.interfaces.Config;
import com.willfp.eco.core.config.interfaces.LoadableConfig;
@@ -29,6 +32,7 @@ import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import org.apache.commons.lang.Validate;
import org.bukkit.Location;
import org.bukkit.NamespacedKey;
import org.bukkit.command.CommandMap;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Mob;
@@ -47,18 +51,18 @@ import java.util.UUID;
import java.util.logging.Logger;
/**
* Holds the instance of eco for bridging between the frontend
* and backend.
* Holds the instance of eco for bridging between the frontend and backend.
* <p>
* <strong>Do not use this in your plugins!</strong> It can and will contain
* breaking changes between minor versions and even patches, and you will create
* compatibility issues by. All parts of this have been abstracted
* into logically named API components that you can use.
* breaking changes between minor versions and even patches, and you will create compatibility
* issues by. All parts of this have been abstracted into logically named API components that you
* can use.
*
* @see Eco#get()
*/
@ApiStatus.Internal
public interface Eco {
/**
* Create a scheduler.
*
@@ -71,7 +75,7 @@ public interface Eco {
/**
* Create an event manager.
*
* @param plugin The plugin.
* @param plugin The plugin.F
* @return The event manager.
*/
@NotNull
@@ -136,7 +140,6 @@ public interface Eco {
*
* @param plugin The plugin.
*/
@NotNull
void createPAPIIntegration(@NotNull EcoPlugin plugin);
/**
@@ -156,6 +159,40 @@ public interface Eco {
@NotNull
EcoPlugin getEcoPlugin();
/**
* Create PluginCommandBase implementation of {@link PluginCommand}.
*
* @param parentDelegate the enclosing class of this implementation.
* @param plugin the plugin.
* @param name the name of the command.
* @param permission the permission of the command.
* @param playersOnly if the command is players only.
* @return The PluginCommandBase implementation
*/
@NotNull
PluginCommandBase createPluginCommand(@NotNull CommandBase parentDelegate,
@NotNull EcoPlugin plugin,
@NotNull String name,
@NotNull String permission,
boolean playersOnly);
/**
* Create CommandBase implementation of {@link com.willfp.eco.core.command.impl.Subcommand Subcommand}.
*
* @param parentDelegate the enclosing class of this implementation.
* @param plugin the plugin.
* @param name the name of the command.
* @param permission the permission of the command.
* @param playersOnly if the command is players only.
* @return The CommandBase implementation
*/
@NotNull
CommandBase createSubcommand(@NotNull CommandBase parentDelegate,
@NotNull EcoPlugin plugin,
@NotNull String name,
@NotNull String permission,
boolean playersOnly);
/**
* Updatable config.
*
@@ -169,6 +206,7 @@ public interface Eco {
* @param requiresChangesToSave If the config must be changed in order to save the config.
* @return The config implementation.
*/
@NotNull
LoadableConfig createUpdatableConfig(@NotNull String configName,
@NotNull PluginLike plugin,
@NotNull String subDirectoryPath,
@@ -189,6 +227,7 @@ public interface Eco {
* @param requiresChangesToSave If the config must be changed in order to save the config.
* @return The config implementation.
*/
@NotNull
LoadableConfig createLoadableConfig(@NotNull String configName,
@NotNull PluginLike plugin,
@NotNull String subDirectoryPath,
@@ -202,6 +241,7 @@ public interface Eco {
* @param config The handle.
* @return The config implementation.
*/
@NotNull
Config wrapConfigurationSection(@NotNull ConfigurationSection config);
/**
@@ -211,6 +251,7 @@ public interface Eco {
* @param type The config type.
* @return The config implementation.
*/
@NotNull
Config createConfig(@NotNull Map<String, Object> values,
@NotNull ConfigType type);
@@ -221,6 +262,7 @@ public interface Eco {
* @param type The type.
* @return The config implementation.
*/
@NotNull
Config createConfig(@NotNull String contents,
@NotNull ConfigType type);
@@ -331,6 +373,7 @@ public interface Eco {
*
* @return The keys.
*/
@NotNull
Set<PersistentDataKey<?>> getRegisteredPersistentDataKeys();
/**
@@ -339,6 +382,7 @@ public interface Eco {
* @param uuid The UUID.
* @return The profile.
*/
@NotNull
PlayerProfile loadPlayerProfile(@NotNull UUID uuid);
/**
@@ -346,6 +390,7 @@ public interface Eco {
*
* @return The profile.
*/
@NotNull
ServerProfile getServerProfile();
/**
@@ -369,9 +414,8 @@ public interface Eco {
/**
* Create a {@link NamespacedKey} quickly
* <p>
* Bypasses the constructor, allowing for the creation of invalid keys,
* therefore this is considered unsafe and should only be called after
* the key has been confirmed to be valid.
* Bypasses the constructor, allowing for the creation of invalid keys, therefore this is
* considered unsafe and should only be called after the key has been confirmed to be valid.
*
* @param namespace The namespace.
* @param key The key.
@@ -499,8 +543,26 @@ public interface Eco {
Menu getOpenMenu(@NotNull Player player);
/**
* Get the instance of eco; the bridge between the api frontend
* and the implementation backend.
* Sync commands.
*/
void syncCommands();
/**
* Get the command map.
*
* @return The command map.
*/
@NotNull CommandMap getCommandMap();
/**
* Unregister a command.
*
* @param command The command.
*/
void unregisterCommand(@NotNull final PluginCommand command);
/**
* Get the instance of eco; the bridge between the api frontend and the implementation backend.
*
* @return The instance of eco.
*/
@@ -514,6 +576,7 @@ public interface Eco {
*/
@ApiStatus.Internal
final class Instance {
/**
* Instance of eco.
*/

View File

@@ -344,6 +344,13 @@ public abstract class EcoPlugin extends JavaPlugin implements PluginLike {
this.configHandler = Eco.get().createConfigHandler(this);
this.langYml = this.createLangYml();
if (!this.langYml.isValid()) {
this.getLogger().warning("Notify plugin authors " + String.join(", ", this.getDescription().getAuthors()) + " that");
this.getLogger().warning("they are missing crucial lang.yml keys! They can be found");
this.getLogger().warning("in the LangYml class.");
}
this.configYml = this.createConfigYml();
Eco.get().addNewPlugin(this);
@@ -699,7 +706,15 @@ public abstract class EcoPlugin extends JavaPlugin implements PluginLike {
* @return lang.yml.
*/
protected LangYml createLangYml() {
return new LangYml(this);
try {
return new LangYml(this);
} catch (NullPointerException e) {
this.getLogger().severe("Failed to load lang.yml!");
this.getLogger().severe("For the developer of this plugin: make sure you have a lang.yml");
e.printStackTrace();
Bukkit.getPluginManager().disablePlugin(this);
return null;
}
}
/**
@@ -710,7 +725,15 @@ public abstract class EcoPlugin extends JavaPlugin implements PluginLike {
* @return config.yml.
*/
protected ConfigYml createConfigYml() {
return new ConfigYml(this);
try {
return new ConfigYml(this);
} catch (NullPointerException e) {
this.getLogger().severe("Failed to load config.yml!");
this.getLogger().severe("For the developer of this plugin: make sure you have a config.yml");
e.printStackTrace();
Bukkit.getPluginManager().disablePlugin(this);
return null;
}
}
/**

View File

@@ -221,7 +221,7 @@ public final class PluginProps {
/**
* Create new props from known values.
*
* <p>
* Marked as internal as this method will break whenever the properties themselves
* are updated (e.g. if a new property is added) - so to prevent any potential
* backwards-compatibility bugs, this method cannot be invoked outside eco itself.

View File

@@ -70,7 +70,10 @@ public class Prerequisite {
/**
* Requires the server to be running an implementation of BungeeCord.
*
* @deprecated This will never return true.
*/
@Deprecated(since = "6.49.0", forRemoval = true)
public static final Prerequisite HAS_BUNGEECORD = new Prerequisite(
() -> ClassUtils.exists("net.md_5.bungee.api.event.ServerConnectedEvent"),
"Requires server to be running BungeeCord (or a fork)"
@@ -78,7 +81,10 @@ public class Prerequisite {
/**
* Requires the server to be running an implementation of Velocity.
*
* @deprecated This will never return true.
*/
@Deprecated(since = "6.49.0", forRemoval = true)
public static final Prerequisite HAS_VELOCITY = new Prerequisite(
() -> ClassUtils.exists("com.velocitypowered.api.event.player.ServerConnectedEvent"),
"Requires server to be running Velocity (or a fork)"

View File

@@ -1,30 +1,37 @@
package com.willfp.eco.core.command;
import com.willfp.eco.core.EcoPlugin;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
/**
* Interface for all command implementations.
* Generic interface for commands.
*/
@SuppressWarnings("removal")
@SuppressWarnings("null")
public interface CommandBase {
/**
* Get command name.
*
* @return The name.
*/
String getName();
@NotNull String getName();
/**
* Get command permission.
*
* @return The permission.
*/
String getPermission();
@NotNull String getPermission();
/**
* If only players can execute the command.
@@ -39,79 +46,237 @@ public interface CommandBase {
* @param command The subcommand.
* @return The parent command.
*/
CommandBase addSubcommand(@NotNull CommandBase command);
@NotNull CommandBase addSubcommand(@NotNull CommandBase command);
/**
* Get the subcommands of the command.
*
* @return The subcommands.
*/
@NotNull List<CommandBase> getSubcommands();
/**
* Intended for returning the enclosing CommandBase,
* when this instance is serving as the delegate command base.
*
* @return the wrapping object of this delegate.
*/
default @NotNull CommandBase getWrapped() {
return this;
}
/**
* Handle command execution.
* <p>
* Marked as default void with no implementation for backwards compatibility.
* This will always be called on command execution.
*
* @param sender The sender.
* @param args The args.
* @throws NotificationException naturally, this is handled as a part of the command system.
*/
default void onExecute(@NotNull CommandSender sender,
@NotNull List<String> args) {
default void onExecute(@NotNull final CommandSender sender, @NotNull final List<String> args) throws NotificationException {
// Do nothing.
}
/**
* Handle command execution from players.
* <p>
* This will only be called if the sender is a player.
*
* @param sender The sender.
* @param args The args.
* @throws NotificationException naturally, this is handled as a part of the command system.
*/
default void onExecute(@NotNull final Player sender, @NotNull final List<String> args) throws NotificationException {
// Do nothing.
}
/**
* Handle tab completion.
* <p>
* Marked as default void with no implementation for backwards compatibility.
* This will always be called on tab completion.
*
* @param sender The sender.
* @param args The args.
* @return The results.
*/
default List<String> tabComplete(@NotNull CommandSender sender,
@NotNull List<String> args) {
@NotNull
default List<String> tabComplete(@NotNull final CommandSender sender, @NotNull final List<String> args) {
return new ArrayList<>();
}
/**
* Handle tab completion.
* <p>
* This will only be called if the sender is a player.
*
* @param sender The sender.
* @param args The args.
* @return The results.
*/
@NotNull
default List<String> tabComplete(@NotNull final Player sender, @NotNull final List<String> args) {
return new ArrayList<>();
}
/**
* Throws an {@link NotificationException} relating to a specific lang.yml key.
* <p>
* This is automatically handled with eco, and should not be surrounded by a
* try/catch block.
*
* @param key The lang.yml key for the message to be sent.
* @throws NotificationException always.
*/
default void notify(@NotNull final String key) throws NotificationException {
throw new NotificationException(key);
}
/**
* Throws an {@link NotificationException} relating to a specific lang.yml key
* if the passed object is null.
* <p>
* This is automatically handled with eco, and should not be surrounded by a
* try/catch block.
*
* @param obj The object to test.
* @param key The lang.yml key for the message to be sent.
* @param <T> The object type.
* @return Returns the object, definitely not-null.
* @throws NotificationException If the object is null.
*/
@NotNull
default <T> T notifyNull(@Nullable final T obj,
@NotNull final String key) throws NotificationException {
if (Objects.isNull(obj)) {
notify(key);
}
return Objects.requireNonNull(obj);
}
/**
* Throws an {@link NotificationException} relating to a specific lang.yml key
* if the passed object doesn't match the predicate.
* <p>
* This is automatically handled with eco, and should not be surrounded by a
* try/catch block.
*
* @param obj The object to test.
* @param key The lang.yml key for the message to be sent.
* @param predicate The predicate to test the object against.
* @param <T> The type of the object.
* @return Returns the object, definitely not-null.
* @throws NotificationException If the object doesn't satisfy the predicate.
*/
@NotNull
default <T> T notifyFalse(@NotNull final T obj,
@NotNull final String key,
@NotNull final Predicate<T> predicate) throws NotificationException {
notifyFalse(predicate.test(obj), key);
return obj;
}
/**
* Throws an {@link NotificationException} relating to a specific lang.yml key
* if a condition is false.
* <p>
* This is automatically handled with eco, and should not be surrounded by a
* try/catch block.
*
* @param condition The condition to test.
* @param key The lang.yml key for the message to be sent.
* @return True.
* @throws NotificationException If the condition is false.
*/
default boolean notifyFalse(final boolean condition,
@NotNull final String key) throws NotificationException {
if (!condition) {
notify(key);
}
return true;
}
/**
* Throws an {@link NotificationException} relating to a specific lang.yml key
* if the passed string doesn't relate to a currently online player.
* <p>
* This is automatically handled with eco, and should not be surrounded by a
* try/catch block.
*
* @param playerName The player name.
* @param key The lang.yml key for the message to be sent.
* @return Returns the player, definitely not-null.
* @throws NotificationException If the player name is invalid.
*/
@NotNull
default Player notifyPlayerRequired(@Nullable final String playerName, @NotNull final String key) throws NotificationException {
if (playerName == null) {
notify(key);
}
assert playerName != null;
final Player player = Bukkit.getPlayer(playerName);
notifyNull(player, key);
return Objects.requireNonNull(player);
}
/**
* Throws an {@link NotificationException} relating to a specific lang.yml key
* if the passed string doesn't relate to a player on the server.
* <p>
* This is automatically handled with eco, and should not be surrounded by a
* try/catch block.
*
* @param playerName The player name.
* @param key The lang.yml key for the message to be sent.
* @return Returns the offline player, definitely not-null.
* @throws NotificationException If the player name is invalid.
*/
@NotNull
default OfflinePlayer notifyOfflinePlayerRequired(@Nullable final String playerName,
@NotNull final String key) throws NotificationException {
if (playerName == null) {
notify(key);
}
assert playerName != null;
@SuppressWarnings("deprecation") final OfflinePlayer player = Bukkit.getOfflinePlayer(playerName);
boolean hasPlayedBefore = player.hasPlayedBefore() || player.isOnline();
notifyFalse(!hasPlayedBefore, key);
return player;
}
/**
* Throws an exception containing a langYml key if player doesn't have permission.
*
* @param player The player.
* @param permission The permission.
* @param key The lang.yml key for the message to be sent.
* @return The player.
* @throws NotificationException If the player doesn't have the required permission.
*/
@NotNull
default Player notifyPermissionRequired(@NotNull final Player player,
@NotNull final String permission,
@NotNull final String key) throws NotificationException {
return notifyFalse(player, key, p -> p.hasPermission(permission));
}
/**
* Get the plugin.
*
* @return The plugin.
*/
EcoPlugin getPlugin();
/**
* Get the handler.
*
* @return The handler.
* @see CommandHandler
* @deprecated Use {@link CommandBase#onExecute(CommandSender, List)} instead.
*/
@Deprecated(forRemoval = true)
CommandHandler getHandler();
/**
* Set the handler.
*
* @param handler The handler.
* @see CommandHandler
* @deprecated Handlers have been deprecated.
*/
@Deprecated(forRemoval = true)
void setHandler(@NotNull CommandHandler handler);
/**
* Get the tab completer.
*
* @return The tab completer.
* @see TabCompleteHandler
* @deprecated Use {@link CommandBase#tabComplete(CommandSender, List)} instead.
*/
@Deprecated(forRemoval = true)
TabCompleteHandler getTabCompleter();
/**
* Set the tab completer.
*
* @param handler The handler.
* @see TabCompleteHandler
* @deprecated Handlers have been deprecated.
*/
@Deprecated(forRemoval = true)
void setTabCompleter(@NotNull TabCompleteHandler handler);
}

View File

@@ -1,29 +0,0 @@
package com.willfp.eco.core.command;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* A command handler handles the actual code for a command.
* <p>
* The replacement for {@link org.bukkit.command.CommandExecutor#onCommand(CommandSender, Command, String, String[])}
*
* @see CommandBase
* @deprecated Handlers have been deprecated. This legacy system will eventually be removed,
* update to use the new system: {@link CommandBase#onExecute(CommandSender, List)}.
*/
@FunctionalInterface
@Deprecated(since = "6.17.0", forRemoval = true)
public interface CommandHandler {
/**
* The code to be called on execution.
*
* @param sender The sender.
* @param args The arguments.
*/
void onExecute(@NotNull CommandSender sender,
@NotNull List<String> args);
}

View File

@@ -0,0 +1,34 @@
package com.willfp.eco.core.command;
/**
* A notification exception is thrown when {@link org.bukkit.command.CommandSender}s don't
* specify valid arguments in commands.
* <p>
* Methods in eco that throw this will contain automatic handling and thus
* should not be surrounded by try / catch blocks.
*/
public class NotificationException extends Exception {
/**
* The key for the lang.yml message to be sent.
*/
private final String key;
/**
* Creates a notification exception.
*
* @param key The lang key of the notification.
*/
public NotificationException(String key) {
super(key);
this.key = key;
}
/**
* Get the lang key.
*
* @return The lang key.
*/
public String getKey() {
return key;
}
}

View File

@@ -0,0 +1,44 @@
package com.willfp.eco.core.command;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
/**
* Plugin command bases can be registered directly with the server,
* this essentially functions as the interface that is implemented generically
* via {@link com.willfp.eco.core.command.impl.PluginCommand}.
*/
public interface PluginCommandBase extends CommandBase {
/**
* Register the PluginCommandBase to the bukkit commandMap.
*/
void register();
/**
* Unregister the PluginCommandBase from the bukkit commandMap.
*/
void unregister();
/**
* Get aliases. Leave null if this command is from plugin.yml.
*
* @return The aliases.
*/
@NotNull
default List<String> getAliases() {
return new ArrayList<>();
}
/**
* Get description.
*
* @return The description.
*/
@Nullable
default String getDescription() {
return null;
}
}

View File

@@ -1,30 +0,0 @@
package com.willfp.eco.core.command;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* A Tab Complete handler handles the actual tab-completion code.
* <p>
* The replacement for {@link org.bukkit.command.TabCompleter#onTabComplete(CommandSender, Command, String, String[])}
*
* @see CommandBase
* @deprecated Handlers have been deprecated. This legacy system will eventually be removed,
* update to use the new system: {@link CommandBase#tabComplete(CommandSender, List)}
*/
@FunctionalInterface
@Deprecated(since = "6.17.0", forRemoval = true)
public interface TabCompleteHandler {
/**
* Handle Tab Completion.
*
* @param sender The sender.
* @param args The arguments.
* @return The tab completion results.
*/
List<String> tabComplete(@NotNull CommandSender sender,
@NotNull List<String> args);
}

View File

@@ -0,0 +1,72 @@
package com.willfp.eco.core.command.impl;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.PluginIdentifiableCommand;
import org.bukkit.command.TabCompleter;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* Delegates a bukkit command to an eco command (for registrations).
*
* @deprecated Internal command implementations have been removed from the API.
*/
@Deprecated(forRemoval = true, since = "6.49.0")
public final class DelegatedBukkitCommand extends Command implements TabCompleter, PluginIdentifiableCommand {
/**
* The delegate command.
*/
private final PluginCommand delegate;
/**
* Create a new delegated command.
*
* @param delegate The delegate.
*/
public DelegatedBukkitCommand(@NotNull final PluginCommand delegate) {
super(delegate.getName());
this.delegate = delegate;
}
@Override
public boolean execute(@NotNull final CommandSender commandSender,
@NotNull final String label,
@NotNull final String[] args) {
return false;
}
@Override
public List<String> onTabComplete(@NotNull final CommandSender commandSender,
@NotNull final Command command,
@NotNull final String label,
@NotNull final String[] args) {
return List.of();
}
@NotNull
@Override
public Plugin getPlugin() {
return this.delegate.getPlugin();
}
@Override
public @NotNull String getPermission() {
return this.delegate.getPermission();
}
@NotNull
@Override
public String getDescription() {
return this.delegate.getDescription() == null ? "" : this.delegate.getDescription();
}
@NotNull
@Override
public List<String> getAliases() {
return this.delegate.getAliases();
}
}

View File

@@ -1,287 +0,0 @@
package com.willfp.eco.core.command.impl;
import com.willfp.eco.core.EcoPlugin;
import com.willfp.eco.core.command.CommandBase;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.util.StringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
/**
* Abstract class for commands that can be handled.
* <p>
* Handled commands have a method to pass in raw input from bukkit commands
* in order to execute the command-specific code. It's essentially an internal
* layer, hence why it's a package-private class.
*/
@SuppressWarnings({"DeprecatedIsStillUsed", "removal"})
abstract class HandledCommand implements CommandBase {
/**
* The plugin.
*/
private final EcoPlugin plugin;
/**
* The name of the command.
*/
private final String name;
/**
* The permission required to execute the command.
* <p>
* Written out as a string for flexibility with subclasses.
*/
private final String permission;
/**
* Should the command only be allowed to be executed by players?
* <p>
* In other worlds, only allowed to be executed by console.
*/
private final boolean playersOnly;
/**
* The actual code to be executed in the command.
*/
@Deprecated
@Nullable
private com.willfp.eco.core.command.CommandHandler handler = null;
/**
* The tab completion code to be executed in the command.
*/
@Deprecated
@Nullable
private com.willfp.eco.core.command.TabCompleteHandler tabCompleter = null;
/**
* All subcommands for the command.
*/
private final List<CommandBase> subcommands;
/**
* Create a new command.
* <p>
* The name cannot be the same as an existing command as this will conflict.
*
* @param plugin Instance of a plugin.
* @param name The name used in execution.
* @param permission The permission required to execute the command.
* @param playersOnly If only players should be able to execute this command.
*/
HandledCommand(@NotNull final EcoPlugin plugin,
@NotNull final String name,
@NotNull final String permission,
final boolean playersOnly) {
this.plugin = plugin;
this.name = name;
this.permission = permission;
this.playersOnly = playersOnly;
this.subcommands = new ArrayList<>();
}
/**
* Add a subcommand to the command.
*
* @param subcommand The subcommand.
* @return The parent command.
*/
@Override
public final CommandBase addSubcommand(@NotNull final CommandBase subcommand) {
subcommands.add(subcommand);
return this;
}
/**
* Get the plugin.
*
* @return The plugin.
*/
@Override
public EcoPlugin getPlugin() {
return this.plugin;
}
/**
* Handle the command.
*
* @param sender The sender.
* @param args The arguments.
*/
protected final void handle(@NotNull final CommandSender sender,
@NotNull final String[] args) {
if (!canExecute(sender, this, this.getPlugin())) {
return;
}
if (args.length > 0) {
for (CommandBase subcommand : this.getSubcommands()) {
if (subcommand.getName().equalsIgnoreCase(args[0])) {
if (!canExecute(sender, subcommand, this.getPlugin())) {
return;
}
((HandledCommand) subcommand).handle(sender, Arrays.copyOfRange(args, 1, args.length));
return;
}
}
}
if (this.isPlayersOnly() && !(sender instanceof Player)) {
sender.sendMessage(this.getPlugin().getLangYml().getMessage("not-player"));
return;
}
if (this.getHandler() != null) {
this.getHandler().onExecute(sender, Arrays.asList(args));
} else {
this.onExecute(sender, Arrays.asList(args));
}
}
/**
* Handle the tab completion.
*
* @param sender The sender.
* @param args The arguments.
* @return The tab completion results.
*/
protected final List<String> handleTabCompletion(@NotNull final CommandSender sender,
@NotNull final String[] args) {
if (!sender.hasPermission(this.getPermission())) {
return null;
}
if (args.length == 1) {
List<String> completions = new ArrayList<>();
StringUtil.copyPartialMatches(
args[0],
this.getSubcommands().stream()
.filter(subCommand -> sender.hasPermission(subCommand.getPermission()))
.map(CommandBase::getName)
.collect(Collectors.toList()),
completions
);
Collections.sort(completions);
if (!completions.isEmpty()) {
return completions;
}
}
if (args.length >= 2) {
HandledCommand command = null;
for (CommandBase subcommand : this.getSubcommands()) {
if (!sender.hasPermission(subcommand.getPermission())) {
continue;
}
if (args[0].equalsIgnoreCase(subcommand.getName())) {
command = (HandledCommand) subcommand;
}
}
if (command != null) {
return command.handleTabCompletion(sender, Arrays.copyOfRange(args, 1, args.length));
}
}
if (this.getTabCompleter() != null) {
return this.getTabCompleter().tabComplete(sender, Arrays.asList(args));
} else {
return this.tabComplete(sender, Arrays.asList(args));
}
}
/**
* If a sender can execute the command.
*
* @param sender The sender.
* @param command The command.
* @param plugin The plugin.
* @return If the sender can execute.
*/
public static boolean canExecute(@NotNull final CommandSender sender,
@NotNull final CommandBase command,
@NotNull final EcoPlugin plugin) {
if (!sender.hasPermission(command.getPermission()) && sender instanceof Player) {
sender.sendMessage(plugin.getLangYml().getNoPermission());
return false;
}
return true;
}
/**
* Get the command name.
*
* @return The name.
*/
public String getName() {
return this.name;
}
/**
* Get the permission required to execute the command.
*
* @return The permission.
*/
public String getPermission() {
return this.permission;
}
/**
* Get if the command can only be executed by players.
*
* @return If players only.
*/
public boolean isPlayersOnly() {
return this.playersOnly;
}
/**
* Get the subcommands of the command.
*
* @return The subcommands.
*/
public List<CommandBase> getSubcommands() {
return this.subcommands;
}
@Deprecated(forRemoval = true)
@Override
public @Nullable com.willfp.eco.core.command.CommandHandler getHandler() {
return this.handler;
}
@Deprecated(forRemoval = true)
@Override
public @Nullable com.willfp.eco.core.command.TabCompleteHandler getTabCompleter() {
return this.tabCompleter;
}
@Deprecated(forRemoval = true)
@Override
public void setHandler(@Nullable final com.willfp.eco.core.command.CommandHandler handler) {
this.handler = handler;
}
@Deprecated(forRemoval = true)
@Override
public void setTabCompleter(@Nullable final com.willfp.eco.core.command.TabCompleteHandler tabCompleter) {
this.tabCompleter = tabCompleter;
}
}

View File

@@ -1,26 +1,28 @@
package com.willfp.eco.core.command.impl;
import com.willfp.eco.core.Eco;
import com.willfp.eco.core.EcoPlugin;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import com.willfp.eco.core.command.CommandBase;
import com.willfp.eco.core.command.PluginCommandBase;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* PluginCommands are the class to be used instead of CommandExecutor,
* they function as the base command, e.g. {@code /ecoenchants} would be a base command, with each
* subsequent argument functioning as subcommands.
* PluginCommands are the class to be used instead of CommandExecutor, they function as the base
* command, e.g. {@code /ecoenchants} would be a base command, with each subsequent argument
* functioning as subcommands.
* <p>
* The command will not be registered until register() is called.
* <p>
* The name cannot be the same as an existing command as this will conflict.
*/
public abstract class PluginCommand extends HandledCommand implements CommandExecutor, TabCompleter {
public abstract class PluginCommand implements PluginCommandBase {
/**
* The delegate command.
*/
private final PluginCommandBase delegate;
/**
* Create a new command.
*
@@ -33,64 +35,51 @@ public abstract class PluginCommand extends HandledCommand implements CommandExe
@NotNull final String name,
@NotNull final String permission,
final boolean playersOnly) {
super(plugin, name, permission, playersOnly);
this.delegate = Eco.get().createPluginCommand(this, plugin, name, permission, playersOnly);
}
/**
* Registers the command with the server,
* <p>
* Requires the command name to exist, defined in plugin.yml.
*/
public final void register() {
org.bukkit.command.PluginCommand command = Bukkit.getPluginCommand(this.getName());
assert command != null;
command.setExecutor(this);
command.setTabCompleter(this);
}
/**
* Internal implementation used to clean up boilerplate.
* Used for parity with {@link CommandExecutor#onCommand(CommandSender, Command, String, String[])}.
*
* @param sender The executor of the command.
* @param command The bukkit command.
* @param label The name of the executed command.
* @param args The arguments of the command (anything after the physical command name)
* @return If the command was processed by the linked {@link EcoPlugin}
*/
@Override
public final boolean onCommand(@NotNull final CommandSender sender,
@NotNull final Command command,
@NotNull final String label,
@NotNull final String[] args) {
if (!command.getName().equalsIgnoreCase(this.getName())) {
return false;
}
this.handle(sender, args);
return true;
public @NotNull String getName() {
return delegate.getName();
}
/**
* Internal implementation used to clean up boilerplate.
* Used for parity with {@link TabCompleter#onTabComplete(CommandSender, Command, String, String[])}.
*
* @param sender The executor of the command.
* @param command The bukkit command.
* @param label The name of the executed command.
* @param args The arguments of the command (anything after the physical command name).
* @return The list of tab-completions.
*/
@Override
public @Nullable List<String> onTabComplete(@NotNull final CommandSender sender,
@NotNull final Command command,
@NotNull final String label,
@NotNull final String[] args) {
if (!command.getName().equalsIgnoreCase(this.getName())) {
return null;
}
public @NotNull String getPermission() {
return delegate.getPermission();
}
return this.handleTabCompletion(sender, args);
@Override
public boolean isPlayersOnly() {
return delegate.isPlayersOnly();
}
@Override
public @NotNull CommandBase addSubcommand(@NotNull CommandBase command) {
return delegate.addSubcommand(command);
}
@Override
public @NotNull List<CommandBase> getSubcommands() {
return delegate.getSubcommands();
}
@Override
public @NotNull CommandBase getWrapped() {
return this;
}
@Override
public void register() {
delegate.register();
}
@Override
public void unregister() {
delegate.unregister();
}
@Override
public EcoPlugin getPlugin() {
return delegate.getPlugin();
}
}

View File

@@ -1,13 +1,21 @@
package com.willfp.eco.core.command.impl;
import com.willfp.eco.core.Eco;
import com.willfp.eco.core.EcoPlugin;
import com.willfp.eco.core.command.CommandBase;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* Subcommands can be added to PluginCommands or to other Subcommands.
* A command implementation that must exist as a subcommand (i.e. cannot be registered directly).
*/
public abstract class Subcommand extends HandledCommand {
public abstract class Subcommand implements CommandBase {
/**
* The delegate command.
*/
private final CommandBase delegate;
/**
* Create subcommand.
*
@@ -20,7 +28,7 @@ public abstract class Subcommand extends HandledCommand {
@NotNull final String name,
@NotNull final String permission,
final boolean playersOnly) {
super(plugin, name, permission, playersOnly);
this.delegate = Eco.get().createSubcommand(this, plugin, name, permission, playersOnly);
}
/**
@@ -33,6 +41,41 @@ public abstract class Subcommand extends HandledCommand {
protected Subcommand(@NotNull final EcoPlugin plugin,
@NotNull final String name,
@NotNull final CommandBase parent) {
super(plugin, name, parent.getPermission(), parent.isPlayersOnly());
this(plugin, name, parent.getPermission(), parent.isPlayersOnly());
}
@Override
public @NotNull String getName() {
return delegate.getName();
}
@Override
public @NotNull String getPermission() {
return delegate.getPermission();
}
@Override
public boolean isPlayersOnly() {
return delegate.isPlayersOnly();
}
@Override
public @NotNull CommandBase addSubcommand(@NotNull CommandBase command) {
return delegate.addSubcommand(command);
}
@Override
public @NotNull List<CommandBase> getSubcommands() {
return delegate.getSubcommands();
}
@Override
public @NotNull CommandBase getWrapped() {
return this;
}
@Override
public EcoPlugin getPlugin() {
return delegate.getPlugin();
}
}

View File

@@ -6,10 +6,32 @@ import com.willfp.eco.core.config.ConfigType;
import com.willfp.eco.util.StringUtils;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* Default plugin lang.yml.
*/
public class LangYml extends BaseConfig {
/**
* The messages key.
*/
public static final String KEY_MESSAGES = "messages";
/**
* The prefix key.
*/
public static final String KEY_PREFIX = "messages.prefix";
/**
* The no permission key.
*/
public static final String KEY_NO_PERMISSION = "messages.no-permission";
/**
* The not player key.
*/
public static final String KEY_NOT_PLAYER = "messages.not-player";
/**
* Lang.yml.
*
@@ -19,13 +41,31 @@ public class LangYml extends BaseConfig {
super("lang", plugin, false, ConfigType.YAML);
}
/**
* lang.yml requires certain keys to be present.
* <p>
* If the lang.yml does not contain these keys, it is considered to be
* invalid and thus will show a warning in console.
*
* @return If valid.
*/
public boolean isValid() {
for (String key : List.of(KEY_MESSAGES, KEY_PREFIX, KEY_NO_PERMISSION, KEY_NOT_PLAYER)) {
if (!this.has(key)) {
return false;
}
}
return true;
}
/**
* Get the prefix for messages in chat.
*
* @return The prefix.
*/
public String getPrefix() {
return this.getFormattedString("messages.prefix");
return this.getFormattedString(KEY_PREFIX);
}
/**
@@ -34,7 +74,7 @@ public class LangYml extends BaseConfig {
* @return The message.
*/
public String getNoPermission() {
return getPrefix() + this.getFormattedString("messages.no-permission");
return getPrefix() + this.getFormattedString(KEY_NO_PERMISSION);
}
/**
@@ -56,6 +96,6 @@ public class LangYml extends BaseConfig {
*/
public String getMessage(@NotNull final String message,
@NotNull final StringUtils.FormatOption option) {
return getPrefix() + this.getFormattedString("messages." + message, option);
return getPrefix() + this.getFormattedString(KEY_MESSAGES + "." + message, option);
}
}

View File

@@ -134,7 +134,7 @@ public final class PersistentDataKey<T> {
if (this == o) {
return true;
}
if (!(o instanceof PersistentDataKey that)) {
if (!(o instanceof PersistentDataKey<?> that)) {
return false;
}
return Objects.equals(this.getKey(), that.getKey());

View File

@@ -99,7 +99,7 @@ public final class PersistentDataKeyType<T> {
if (this == that) {
return true;
}
if (!(that instanceof PersistentDataKeyType type)) {
if (!(that instanceof PersistentDataKeyType<?> type)) {
return false;
}
return Objects.equals(this.name, type.name);

View File

@@ -63,11 +63,29 @@ public interface Menu {
* @param player The player
* @param menu The menu.
* @return The slot.
* @deprecated Menu shouldn't be a parameter.
*/
default Slot getSlot(int row,
int column,
@NotNull Player player,
@NotNull Menu menu) {
@Deprecated(since = "6.46.0", forRemoval = true)
default Slot getSlot(final int row,
final int column,
@NotNull final Player player,
@NotNull final Menu menu) {
return this.getSlot(row, column, player);
}
/**
* Get a slot at a given row and column.
* <p>
* Defaults to static slot if no reactive slot exists.
*
* @param row The row.
* @param column The column.
* @param player The player
* @return The slot.
*/
default Slot getSlot(final int row,
final int column,
@NotNull final Player player) {
return this.getSlot(row, column);
}

View File

@@ -139,7 +139,7 @@ public interface MenuBuilder extends PageBuilder {
* @return The builder.
*/
default MenuBuilder maxPages(@NotNull final Function<Player, Integer> pages) {
return onRender((player, menu) -> menu.setState(player, Page.MAX_PAGE_KEY, pages.apply(player)));
return this.onRender((player, menu) -> menu.setState(player, Page.MAX_PAGE_KEY, pages.apply(player)));
}
/**
@@ -195,6 +195,16 @@ public interface MenuBuilder extends PageBuilder {
return this;
}
/**
* Add an action to run on build.
*
* @param action The action.
* @return The builder.
*/
default MenuBuilder onBuild(@NotNull Consumer<Menu> action) {
return this;
}
/**
* Build the menu.
*

View File

@@ -0,0 +1,22 @@
package com.willfp.eco.core.gui.menu.events;
import com.willfp.eco.core.gui.menu.MenuEvent;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.Nullable;
/**
* Represents a captive item change.
*
* @param row The row.
* @param column The column.
* @param before The previous item in the slot.
* @param after The new item in the slot.
*/
public record CaptiveItemChangeEvent(
int row,
int column,
@Nullable ItemStack before,
@Nullable ItemStack after
) implements MenuEvent {
}

View File

@@ -83,7 +83,7 @@ public final class Page implements GUIComponent {
delegate = Eco.get().blendMenuState(page, menu);
}
return page.getSlot(row, column, player, delegate);
return page.getSlot(row, column, player);
}
@Override

View File

@@ -1,12 +1,14 @@
package com.willfp.eco.core.gui.slot;
import com.willfp.eco.core.config.interfaces.Config;
import com.willfp.eco.core.fast.FastItemStack;
import com.willfp.eco.core.gui.slot.functional.SlotHandler;
import com.willfp.eco.core.items.Items;
import com.willfp.eco.util.StringUtils;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
@@ -37,7 +39,25 @@ public class ConfigSlot extends CustomSlot {
public ConfigSlot(@NotNull final Config config) {
this.config = config;
SlotBuilder builder = Slot.builder(Items.lookup(config.getString("item")));
ItemStack item = Items.lookup(config.getString("item")).getItem();
SlotBuilder builder = Slot.builder((player, menu) -> {
if (!config.has("lore")) {
return item;
} else {
FastItemStack fast = FastItemStack.wrap(item.clone());
List<String> newLore = new ArrayList<>(fast.getLore());
newLore.addAll(
StringUtils.formatList(
config.getStrings("lore"),
player,
StringUtils.FormatOption.WITH_PLACEHOLDERS
)
);
fast.setLore(newLore);
return fast.unwrap();
}
});
for (ClickType clickType : ClickType.values()) {
builder.onClick(

View File

@@ -4,6 +4,7 @@ import com.willfp.eco.core.gui.menu.Menu;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Base class for custom slot implementations.
@@ -31,7 +32,7 @@ public abstract class CustomSlot implements Slot {
}
@Override
public ItemStack getItemStack(@NotNull final Player player) {
public final @NotNull ItemStack getItemStack(@NotNull final Player player) {
if (delegate == null) {
throw new IllegalStateException("Custom Slot was not initialized!");
}
@@ -40,8 +41,8 @@ public abstract class CustomSlot implements Slot {
}
@Override
public boolean isCaptive(@NotNull final Player player,
@NotNull final Menu menu) {
public final boolean isCaptive(@NotNull final Player player,
@NotNull final Menu menu) {
if (delegate == null) {
throw new IllegalStateException("Custom Slot was not initialized!");
}
@@ -50,7 +51,18 @@ public abstract class CustomSlot implements Slot {
}
@Override
public boolean isCaptiveFromEmpty() {
public final boolean isAllowedCaptive(@NotNull final Player player,
@NotNull final Menu menu,
@Nullable final ItemStack itemStack) {
if (delegate == null) {
throw new IllegalStateException("Custom Slot was not initialized!");
}
return delegate.isAllowedCaptive(player, menu, itemStack);
}
@Override
public final boolean isCaptiveFromEmpty() {
if (delegate == null) {
throw new IllegalStateException("Custom Slot was not initialized!");
}
@@ -59,8 +71,8 @@ public abstract class CustomSlot implements Slot {
}
@Override
public final Slot getActionableSlot(@NotNull final Player player,
@NotNull final Menu menu) {
public final @NotNull Slot getActionableSlot(@NotNull final Player player,
@NotNull final Menu menu) {
return delegate;
}

View File

@@ -5,6 +5,7 @@ import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Base class for custom slot implementations.
@@ -29,19 +30,26 @@ public abstract class ReactiveSlot implements Slot {
@NotNull final Menu menu);
@Override
public ItemStack getItemStack(@NotNull final Player player) {
return new ItemStack(Material.STONE);
public @NotNull ItemStack getItemStack(@NotNull final Player player) {
return new ItemStack(Material.AIR);
}
@Override
public boolean isCaptive(@NotNull final Player player,
@NotNull final Menu menu) {
public final boolean isCaptive(@NotNull final Player player,
@NotNull final Menu menu) {
return getSlot(player, menu).isCaptive(player, menu);
}
@Override
public final Slot getActionableSlot(@NotNull final Player player,
@NotNull final Menu menu) {
public final boolean isAllowedCaptive(@NotNull final Player player,
@NotNull final Menu menu,
@Nullable final ItemStack itemStack) {
return getSlot(player, menu).isAllowedCaptive(player, menu, itemStack);
}
@Override
public final @NotNull Slot getActionableSlot(@NotNull final Player player,
@NotNull final Menu menu) {
return getSlot(player, menu);
}

View File

@@ -9,6 +9,7 @@ import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.function.Function;
@@ -30,6 +31,7 @@ public interface Slot extends GUIComponent {
* @param player The player.
* @return The ItemStack.
*/
@NotNull
ItemStack getItemStack(@NotNull Player player);
/**
@@ -44,6 +46,20 @@ public interface Slot extends GUIComponent {
return false;
}
/**
* If the slot allows a certain item to be placed in it.
*
* @param player The player.
* @param menu The menu.
* @param itemStack The item; use null if the item is unknown.
* @return If captive.
*/
default boolean isAllowedCaptive(@NotNull final Player player,
@NotNull final Menu menu,
@Nullable final ItemStack itemStack) {
return this.isCaptive(player, menu);
}
/**
* Get the actionable slot to be shown.
* <p>
@@ -60,6 +76,7 @@ public interface Slot extends GUIComponent {
* @param menu The menu.
* @return The slot.
*/
@NotNull
default Slot getActionableSlot(@NotNull final Player player,
@NotNull final Menu menu) {
return this;
@@ -125,7 +142,9 @@ public interface Slot extends GUIComponent {
*
* @param provider The provider.
* @return The builder.
* @deprecated This method was written incorrectly, should have been a Player + Menu function.
*/
@Deprecated(since = "6.45.0", forRemoval = true)
static SlotBuilder builder(@NotNull final Function<Player, ItemStack> provider) {
return Eco.get().createSlotBuilder((player, menu) -> provider.apply(player));
}

View File

@@ -1,5 +1,6 @@
package com.willfp.eco.core.gui.slot;
import com.willfp.eco.core.gui.slot.functional.CaptiveFilter;
import com.willfp.eco.core.gui.slot.functional.SlotHandler;
import com.willfp.eco.core.gui.slot.functional.SlotModifier;
import com.willfp.eco.core.gui.slot.functional.SlotUpdater;
@@ -143,7 +144,17 @@ public interface SlotBuilder {
* @param predicate The predicate. Returns true when the slot should not be captive.
* @return The builder.
*/
SlotBuilder notCaptiveFor(@NotNull Predicate<Player> predicate);
SlotBuilder notCaptiveFor(@NotNull final Predicate<Player> predicate);
/**
* Set a whitelist for allowed captive items.
*
* @param filter The filter.
* @return The builder.
*/
default SlotBuilder setCaptiveFilter(@NotNull final CaptiveFilter filter) {
return this;
}
/**
* Set the ItemStack updater.

View File

@@ -0,0 +1,25 @@
package com.willfp.eco.core.gui.slot.functional;
import com.willfp.eco.core.gui.menu.Menu;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Interface to test if a captive slot is allowed to contain an item given a player and a menu.
*/
@FunctionalInterface
public interface CaptiveFilter {
/**
* Get if allowed.
*
* @param player The player.
* @param menu The menu.
* @param itemStack The item.
* @return If captive.
*/
boolean isAllowed(@NotNull Player player,
@NotNull Menu menu,
@Nullable ItemStack itemStack);
}

View File

@@ -33,10 +33,15 @@ public final class McmmoManager {
* @return The bonus drop count.
*/
public static int getBonusDropCount(@NotNull final Block block) {
int finalValue = 0;
for (McmmoIntegration mcmmoIntegration : REGISTERED) {
return mcmmoIntegration.getBonusDropCount(block);
finalValue += mcmmoIntegration.getBonusDropCount(block);
}
return 0;
return finalValue;
}
/**
@@ -47,7 +52,10 @@ public final class McmmoManager {
*/
public static boolean isFake(@NotNull final Event event) {
for (McmmoIntegration mcmmoIntegration : REGISTERED) {
return mcmmoIntegration.isFake(event);
if (mcmmoIntegration.isFake(event)) {
return true;
}
}
return false;
}

View File

@@ -1,6 +1,8 @@
package com.willfp.eco.core.integrations.shop;
import com.willfp.eco.core.integrations.Integration;
import com.willfp.eco.core.price.Price;
import com.willfp.eco.core.price.impl.PriceFree;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.bukkit.inventory.ItemStack;
@@ -29,12 +31,41 @@ public interface ShopIntegration extends Integration {
return null;
}
/**
* Get if an item is sellable for a player.
*
* @param itemStack The item.
* @param player The player.
* @return If sellable.
*/
default boolean isSellable(@NotNull final ItemStack itemStack,
@NotNull final Player player) {
return false;
}
/**
* Get the value of one of an item for a player.
* <p>
* For example, if you pass in a stack, it will only return the value of <b>one</b> item, not the full stack.
*
* @param itemStack The item.
* @param player The player.
* @return The price.
*/
@NotNull
default Price getUnitValue(@NotNull final ItemStack itemStack,
@NotNull final Player player) {
return new PriceFree();
}
/**
* Get the price of an item.
*
* @param itemStack The item.
* @return The price.
* @deprecated Use getValue instead.
*/
@Deprecated(since = "6.47.0", forRemoval = true)
default double getPrice(@NotNull final ItemStack itemStack) {
// Do nothing unless overridden.
return 0.0;
@@ -46,9 +77,11 @@ public interface ShopIntegration extends Integration {
* @param itemStack The item.
* @param player The player.
* @return The price.
* @deprecated Use getValue instead.
*/
@Deprecated(since = "6.47.0", forRemoval = true)
default double getPrice(@NotNull final ItemStack itemStack,
@NotNull final Player player) {
return getPrice(itemStack);
return getUnitValue(itemStack, player).getValue(player);
}
}

View File

@@ -1,5 +1,7 @@
package com.willfp.eco.core.integrations.shop;
import com.willfp.eco.core.price.Price;
import com.willfp.eco.core.price.impl.PriceFree;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
@@ -36,12 +38,57 @@ public final class ShopManager {
}
}
/**
* Get if an item is sellable for a player.
*
* @param itemStack The item.
* @param player The player.
* @return If sellable.
*/
public static boolean isSellable(@Nullable final ItemStack itemStack,
@NotNull final Player player) {
if (itemStack == null) {
return false;
}
for (ShopIntegration integration : REGISTERED) {
return integration.isSellable(itemStack, player);
}
return false;
}
/**
* Get the value of one of an item for a player.
* <p>
* For example, if you pass in a stack, it will only return the value of <b>one</b> item, not the full stack.
*
* @param itemStack The item.
* @param player The player.
* @return The price.
*/
@NotNull
public static Price getUnitValue(@Nullable final ItemStack itemStack,
@NotNull final Player player) {
if (itemStack == null) {
return new PriceFree();
}
for (ShopIntegration integration : REGISTERED) {
return integration.getUnitValue(itemStack, player);
}
return new PriceFree();
}
/**
* Get the price of an item.
*
* @param itemStack The item.
* @return The price.
* @deprecated Use getValue instead. This will always return 0 as prices depend on players.
*/
@Deprecated(since = "6.47.0", forRemoval = true)
public static double getItemPrice(@Nullable final ItemStack itemStack) {
return getItemPrice(itemStack, null);
}
@@ -52,19 +99,17 @@ public final class ShopManager {
* @param itemStack The item.
* @param player The player.
* @return The price.
* @deprecated Use getValue instead. Null players / null items will always return 0.
*/
@Deprecated(since = "6.47.0", forRemoval = true)
public static double getItemPrice(@Nullable final ItemStack itemStack,
@Nullable final Player player) {
if (itemStack == null) {
if (itemStack == null || player == null) {
return 0.0;
}
for (ShopIntegration shopIntegration : REGISTERED) {
if (player == null) {
return shopIntegration.getPrice(itemStack);
} else {
return shopIntegration.getPrice(itemStack, player);
}
return shopIntegration.getUnitValue(itemStack, player).getValue(player, itemStack.getAmount());
}
return 0.0;

View File

@@ -1,5 +1,7 @@
package com.willfp.eco.core.integrations.shop;
import com.willfp.eco.core.price.Price;
import com.willfp.eco.core.price.impl.PriceEconomy;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.bukkit.event.player.PlayerEvent;
@@ -19,7 +21,12 @@ public class ShopSellEvent extends PlayerEvent {
/**
* The sell price.
*/
private double price;
private Price price;
/**
* The price multiplier.
*/
private double multiplier;
/**
* The item to be sold.
@@ -33,31 +40,64 @@ public class ShopSellEvent extends PlayerEvent {
* @param who The player.
* @param price The price.
* @param item The item.
* @deprecated Use the price system instead.
*/
@Deprecated(since = "6.47.0", forRemoval = true)
public ShopSellEvent(@NotNull final Player who,
final double price,
@Nullable final ItemStack item) {
this(who, new PriceEconomy(price), item);
}
/**
* Create new shop sell event.
*
* @param who The player.
* @param price The price.
* @param item The item.
*/
public ShopSellEvent(@NotNull final Player who,
@NotNull final Price price,
@Nullable final ItemStack item) {
this(who, price, item, 1.0);
}
/**
* Create new shop sell event.
*
* @param who The player.
* @param price The price.
* @param item The item.
* @param multiplier The multiplier.
*/
public ShopSellEvent(@NotNull final Player who,
@NotNull final Price price,
@Nullable final ItemStack item,
final double multiplier) {
super(who);
this.price = price;
this.item = item;
this.multiplier = multiplier;
}
/**
* Get the price.
* Get the value.
*
* @return The price.
* @return The value.
*/
public double getPrice() {
@NotNull
public Price getValue() {
return this.price;
}
/**
* Set the price.
* Set the value.
*
* @param price The price.
* @param price The value.
*/
public void setPrice(final double price) {
public void setValue(@NotNull final Price price) {
this.price = price;
}
@@ -81,6 +121,46 @@ public class ShopSellEvent extends PlayerEvent {
return item != null;
}
/**
* Get the price multiplier.
*
* @return The multiplier.
*/
public double getMultiplier() {
return multiplier;
}
/**
* Set the price multiplier.
*
* @param multiplier The multiplier.
*/
public void setMultiplier(final double multiplier) {
this.multiplier = multiplier;
}
/**
* Get the price.
*
* @return The price.
* @deprecated Use the price system instead.
*/
@Deprecated(since = "6.47.0", forRemoval = true)
public double getPrice() {
return this.getValue().getValue(player);
}
/**
* Set the price.
*
* @param price The price.
* @deprecated Use the price system instead.
*/
@Deprecated(since = "6.47.0", forRemoval = true)
public void setPrice(final double price) {
this.setValue(new PriceEconomy(price));
}
/**
* Bukkit parity.
*

View File

@@ -70,7 +70,7 @@ public abstract class AbstractItemStackBuilder<T extends ItemMeta, U extends Abs
@Override
public U setAmount(final int amount) {
Validate.isTrue(amount >= 1 && amount <= base.getMaxStackSize());
Validate.isTrue(amount >= 1);
base.setAmount(amount);
return (U) this;
}

View File

@@ -44,4 +44,20 @@ public record MathContext(
Collections.emptyList()
);
}
/**
* Copy a MathContext with a player.
*
* @param context The context.
* @param player The player.
* @return The new MathContext.
*/
public static MathContext copyWithPlayer(@NotNull final MathContext context,
@Nullable final Player player) {
return new MathContext(
context.injectableContext(),
player,
context.additionalPlayers()
);
}
}

View File

@@ -0,0 +1,156 @@
package com.willfp.eco.core.price;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* A group of {@link ConfiguredPrice}s in order to show them
* to players in one go.
*/
public final class CombinedDisplayPrice {
/**
* Maps configured prices to multipliers.
*/
private final Map<ConfiguredPrice, Double> prices;
/**
* The player to format for.
*/
private final Player player;
/**
* Initialize a new combined price mapping formatters to multipliers.
*
* @param player The player.
* @param prices The prices.
*/
private CombinedDisplayPrice(@NotNull final Player player,
@NotNull final Map<ConfiguredPrice, Double> prices) {
this.player = player;
this.prices = prices;
}
/**
* Get the display strings.
*
* @return The display strings.
*/
@NotNull
public String[] getDisplayStrings() {
List<String> displayStrings = new ArrayList<>();
for (Map.Entry<ConfiguredPrice, Double> entry : prices.entrySet()) {
displayStrings.add(entry.getKey().getDisplay(player, entry.getValue()));
}
return displayStrings.toArray(new String[0]);
}
/**
* The builder.
*/
public static class Builder {
/**
* All multiplied prices.
*/
private final List<MultipliedPrice> prices = new ArrayList<>();
/**
* The player.
*/
private final Player player;
/**
* Create a new builder.
*
* @param player The player.
*/
Builder(@NotNull final Player player) {
this.player = player;
}
/**
* Add a new price with a certain multiplier.
*
* @param price The price.
* @param multiplier The multiplier.
* @return The builder.
*/
@NotNull
public Builder add(@NotNull final ConfiguredPrice price,
final double multiplier) {
prices.add(new MultipliedPrice(price, multiplier));
return this;
}
/**
* Add a new price.
*
* @param price The price.
* @return The builder.
*/
@NotNull
public Builder add(@NotNull final ConfiguredPrice price) {
return this.add(price, 1D);
}
/**
* Build into a {@link CombinedDisplayPrice}.
*
* @return The combined price.
*/
@NotNull
public CombinedDisplayPrice build() {
Map<ConfiguredPrice, Double> unitPrices = new HashMap<>();
// Take first configured price at each ID as the format for all prices with that ID.
for (MultipliedPrice price : prices) {
// Find the base price.
ConfiguredPrice base = unitPrices.keySet()
.stream()
.filter(it -> it.getIdentifier().equals(price.price().getIdentifier()))
.findFirst()
.orElse(price.price());
// Find the multiplier for a value of 1, e.g. a price that's worth 20 will be 0.05.
double unitMultiplier = 1 / base.getValue(player);
double currentMultiplier = unitPrices.getOrDefault(base, 0D);
currentMultiplier += unitMultiplier * price.price().getValue(player, price.multiplier());
unitPrices.put(base, currentMultiplier);
}
return new CombinedDisplayPrice(player, unitPrices);
}
/**
* A price with a multiplier.
*
* @param price The price.
* @param multiplier The multiplier.
*/
private record MultipliedPrice(
@NotNull ConfiguredPrice price,
double multiplier
) {
}
}
/**
* Create a new builder for a player.
*
* @param player The player.
* @return The builder.
*/
@NotNull
public static Builder builder(@NotNull final Player player) {
return new Builder(player);
}
}

View File

@@ -0,0 +1,177 @@
package com.willfp.eco.core.price;
import com.willfp.eco.core.config.interfaces.Config;
import com.willfp.eco.core.math.MathContext;
import com.willfp.eco.core.price.impl.PriceFree;
import com.willfp.eco.core.serialization.ConfigDeserializer;
import com.willfp.eco.util.NumberUtils;
import com.willfp.eco.util.StringUtils;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
/**
* A price that can be shown to a player.
*/
public final class ConfiguredPrice implements Price {
/**
* The deserializer.
*/
private static final ConfigDeserializer<ConfiguredPrice> DESERIALIZER = new Deserializer();
/**
* Free.
*/
public static final ConfiguredPrice FREE = new ConfiguredPrice(
new PriceFree(),
"Free"
);
/**
* The price.
*/
private final Price price;
/**
* The format string.
*/
private final String formatString;
/**
* Create a new Configured Price.
*
* @param price The price.
* @param formatString The format string.
*/
public ConfiguredPrice(@NotNull final Price price,
@NotNull final String formatString) {
this.price = price;
this.formatString = formatString;
}
@Override
public boolean canAfford(@NotNull final Player player,
final double multiplier) {
return this.price.canAfford(player, multiplier);
}
@Override
public void pay(@NotNull final Player player,
final double multiplier) {
this.price.pay(player, multiplier);
}
@Override
public void giveTo(@NotNull final Player player,
final double multiplier) {
this.price.giveTo(player, multiplier);
}
@Override
public double getValue(@NotNull final Player player,
final double multiplier) {
return this.price.getValue(player, multiplier);
}
@Override
public double getMultiplier(@NotNull final Player player) {
return this.price.getMultiplier(player);
}
@Override
public void setMultiplier(@NotNull final Player player,
final double multiplier) {
this.price.setMultiplier(player, multiplier);
}
@Override
public String getIdentifier() {
return this.price.getIdentifier();
}
/**
* Get the price that this delegates to.
*
* @return The price.
*/
public Price getPrice() {
return price;
}
/**
* Get the display string for a player.
*
* @param player The player.
* @return The display string.
*/
public String getDisplay(@NotNull final Player player) {
return this.getDisplay(player, 1.0);
}
/**
* Get the display string for a player.
*
* @param player The player.
* @param multiplier The multiplier.
* @return The display string.
*/
public String getDisplay(@NotNull final Player player,
final double multiplier) {
return StringUtils.format(
formatString.replace("%value%", NumberUtils.format(this.getPrice().getValue(player, multiplier))),
player,
StringUtils.FormatOption.WITH_PLACEHOLDERS
);
}
/**
* Parse a configured price from config.
*
* @param config The config.
* @return The price, or null if it's invalid.
*/
@Nullable
public static ConfiguredPrice create(@NotNull final Config config) {
return DESERIALIZER.deserialize(config);
}
/**
* Parse a configured price from config.
*
* @param config The config.
* @return The price, or free if invalid.
*/
@NotNull
public static ConfiguredPrice createOrFree(@NotNull final Config config) {
return Objects.requireNonNullElse(create(config), FREE);
}
/**
* The deserializer for {@link ConfiguredPrice}.
*/
private static final class Deserializer implements ConfigDeserializer<ConfiguredPrice> {
@Override
@Nullable
public ConfiguredPrice deserialize(@NotNull final Config config) {
if (!(
config.has("value")
&& config.has("type")
&& config.has("display")
)) {
return null;
}
String formatString = config.getString("display");
Price price = Prices.create(
config.getString("value"),
config.getString("type"),
MathContext.of(config)
);
return new ConfiguredPrice(price, formatString);
}
}
}

View File

@@ -1,34 +1,159 @@
package com.willfp.eco.core.price;
import org.apache.commons.lang.NotImplementedException;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* A price that a player should pay.
* <p>
* There are important implementation details:
* <p>
* For backwards compatibility, all methods are default, however you must override the following:
* <ul>
* <li><code>canAfford(Player, double)</code></li>
* <li><code>pay(Player, double)</code></li>
* <li><code>giveTo(Player, double)</code></li>
* <li><code>getValue(Player, double)</code></li>
* <li><code>getMultiplier(Player)</code></li>
* <li><code>setMultiplier(Player, double)</code></li>
* </ul>
* Otherwise, your implementation will throw {@link NotImplementedException}.
* <p>
* Also, getValue() should always return the value with player multipliers applied.
*/
public interface Price {
/**
* Get if the player can afford the price.
* Get if a player can afford to pay the price.
*
* @param player The player.
* @return If the player can afford.
*/
boolean canAfford(@NotNull Player player);
default boolean canAfford(@NotNull final Player player) {
return this.canAfford(player, 1);
}
/**
* Get if a player can afford to pay x times the price.
*
* @param player The player.
* @param multiplier The multiplier.
* @return If the player can afford.
*/
default boolean canAfford(@NotNull final Player player,
final double multiplier) {
throw new NotImplementedException("Override canAfford(Player, double) in your Price implementation!");
}
/**
* Make the player pay the price.
* <p>
* Only run this if the player can afford the price.
* Check canAfford first.
*
* @param player The player.
*/
void pay(@NotNull Player player);
default void pay(@NotNull final Player player) {
this.pay(player, 1);
}
/**
* Make the player pay the price x times.
* <p>
* Check canAfford first.
*
* @param player The player.
* @param multiplier The multiplier.
*/
default void pay(@NotNull final Player player,
final double multiplier) {
throw new NotImplementedException("Override pay(Player, double) in your Price implementation!");
}
/**
* Give the price to the player.
*
* @param player The player.
*/
default void giveTo(@NotNull final Player player) {
this.giveTo(player, 1);
}
/**
* Give the price to the player x times.
*
* @param player The player.
* @param multiplier The multiplier.
*/
default void giveTo(@NotNull final Player player,
final double multiplier) {
throw new NotImplementedException("Override giveTo(Player, double) in your Price implementation!");
}
/**
* Get the numerical value that backs this price.
*
* @param player The player.
* @return The value.
*/
default double getValue(@NotNull final Player player) {
return getValue(player, 1);
}
/**
* Get the numeral value that backs this price multiplied x times.
*
* @param player The player.
* @param multiplier The multiplier.
* @return The value.
*/
default double getValue(@NotNull final Player player,
final double multiplier) {
throw new NotImplementedException("Override getValue(Player, double) in your Price implementation!");
}
/**
* Get the value multiplier for the player.
*
* @param player The player.
* @return The multiplier.
*/
default double getMultiplier(@NotNull final Player player) {
return 1;
}
/**
* Set the value multiplier for the player.
*
* @param player The player.
* @param multiplier The multiplier.
*/
default void setMultiplier(@NotNull final Player player,
final double multiplier) {
throw new NotImplementedException("Override setMultiplier(Player, double) in your Price implementation!");
}
/**
* Get the identifier of this price (as type/instance checks break with delegation,
* this is used for combining prices, etc.)
* <p>
* By default, this uses the class name, but it's good practice to override this.
* <p>
* It's also good practice to prefix your identifiers with some kind of namespace or
* internal ID, in order to prevent conflicts.
*
* @return The identifier.
*/
default String getIdentifier() {
return this.getClass().getName();
}
/**
* If the price is backed by a value, get it here.
*
* @return The value.
* @deprecated Use getValue(Player) instead.
*/
@Deprecated(since = "6.45.0", forRemoval = true)
default double getValue() {
return 0;
}
@@ -37,7 +162,9 @@ public interface Price {
* If the price is backed by a value, set it here.
*
* @param value The value.
* @deprecated Values shouldn't be fixed. This method should never work.
*/
@Deprecated(since = "6.45.0", forRemoval = true)
default void setValue(final double value) {
// Override when needed.
}

View File

@@ -1,11 +1,16 @@
package com.willfp.eco.core.price;
import com.willfp.eco.core.math.MathContext;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.function.Function;
/**
* Create prices.
* <p>
* You must override one of the create methods.
*/
public interface PriceFactory {
/**
@@ -23,5 +28,19 @@ public interface PriceFactory {
* @param value The value.
* @return The price.
*/
@NotNull Price create(double value);
default @NotNull Price create(final double value) {
return create(MathContext.EMPTY, (ctx) -> value);
}
/**
* Create the price.
*
* @param baseContext The base MathContext.
* @param function The function to use. Should use {@link MathContext#copyWithPlayer(MathContext, Player)} on calls.
* @return The price.
*/
default @NotNull Price create(@NotNull final MathContext baseContext,
@NotNull final Function<MathContext, Double> function) {
return create(function.apply(baseContext));
}
}

View File

@@ -13,6 +13,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
/**
* Class to manage prices.
@@ -65,18 +66,18 @@ public final class Prices {
public static Price create(@NotNull final String expression,
@Nullable final String priceName,
@NotNull final MathContext context) {
double value = NumberUtils.evaluateExpression(
Function<MathContext, Double> function = (ctx) -> NumberUtils.evaluateExpression(
expression,
context
ctx
);
if (value <= 0) {
if (function.apply(context) <= 0) {
return new PriceFree();
}
// Default to economy
if (priceName == null) {
return new PriceEconomy(value);
return new PriceEconomy(context, function);
}
// Find price factory
@@ -90,9 +91,9 @@ public final class Prices {
return new PriceFree();
}
return new PriceItem((int) Math.round(value), item);
return new PriceItem(context, function, item);
} else {
return factory.create(value);
return factory.create(context, function);
}
}

View File

@@ -1,10 +1,16 @@
package com.willfp.eco.core.price.impl;
import com.willfp.eco.core.integrations.economy.EconomyManager;
import com.willfp.eco.core.math.MathContext;
import com.willfp.eco.core.price.Price;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.function.Function;
/**
* Economy-based price (for Vault, Treasury, etc.)
*/
@@ -12,7 +18,17 @@ public final class PriceEconomy implements Price {
/**
* The value of the price.
*/
private double value;
private final Function<MathContext, Double> function;
/**
* The base math context.
*/
private final MathContext baseContext;
/**
* The multipliers.
*/
private final Map<UUID, Double> multipliers = new HashMap<>();
/**
* Create a new economy-based price.
@@ -20,26 +36,58 @@ public final class PriceEconomy implements Price {
* @param value The value.
*/
public PriceEconomy(final double value) {
this.value = value;
this(MathContext.EMPTY, ctx -> value);
}
/**
* Create a new economy-based price.
*
* @param baseContext The base context.
* @param function The function.
*/
public PriceEconomy(@NotNull final MathContext baseContext,
@NotNull final Function<MathContext, Double> function) {
this.baseContext = baseContext;
this.function = function;
}
@Override
public boolean canAfford(@NotNull Player player) {
return EconomyManager.getBalance(player) >= value;
public boolean canAfford(@NotNull final Player player,
final double multiplier) {
return EconomyManager.getBalance(player) >= getValue(player, multiplier);
}
@Override
public void pay(@NotNull Player player) {
EconomyManager.removeMoney(player, value);
public void pay(@NotNull final Player player,
final double multiplier) {
EconomyManager.removeMoney(player, getValue(player, multiplier));
}
@Override
public double getValue() {
return value;
public void giveTo(@NotNull final Player player,
final double multiplier) {
EconomyManager.giveMoney(player, getValue(player, multiplier));
}
@Override
public void setValue(final double value) {
this.value = value;
public double getValue(@NotNull final Player player,
final double multiplier) {
return this.function.apply(MathContext.copyWithPlayer(baseContext, player)) * getMultiplier(player) * multiplier;
}
@Override
public double getMultiplier(@NotNull final Player player) {
return this.multipliers.getOrDefault(player.getUniqueId(), 1.0);
}
@Override
public void setMultiplier(@NotNull final Player player,
final double multiplier) {
this.multipliers.put(player.getUniqueId(), multiplier);
}
@Override
public String getIdentifier() {
return "eco:economy";
}
}

View File

@@ -16,12 +16,42 @@ public final class PriceFree implements Price {
}
@Override
public boolean canAfford(@NotNull Player player) {
public boolean canAfford(@NotNull final Player player,
final double multiplier) {
return true;
}
@Override
public void pay(@NotNull Player player) {
// Do nothing.
public void pay(@NotNull final Player player,
final double multiplier) {
// Nothing.
}
@Override
public void giveTo(@NotNull final Player player,
final double multiplier) {
// Nothing.
}
@Override
public double getMultiplier(@NotNull final Player player) {
return 1.0;
}
@Override
public void setMultiplier(@NotNull final Player player,
final double multiplier) {
// Nothing.
}
@Override
public double getValue(@NotNull final Player player,
final double multiplier) {
return 0;
}
@Override
public String getIdentifier() {
return "eco:free";
}
}

View File

@@ -1,20 +1,33 @@
package com.willfp.eco.core.price.impl;
import com.willfp.eco.core.drops.DropQueue;
import com.willfp.eco.core.items.HashedItem;
import com.willfp.eco.core.items.TestableItem;
import com.willfp.eco.core.math.MathContext;
import com.willfp.eco.core.price.Price;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.function.Function;
/**
* Item-based price.
*/
public final class PriceItem implements Price {
/**
* The base MathContext.
*/
private final MathContext baseContext;
/**
* The amount of items.
*/
private final int amountToRemove;
private final Function<MathContext, Double> function;
/**
* The item.
@@ -22,14 +35,33 @@ public final class PriceItem implements Price {
private final TestableItem item;
/**
* Create a new economy-based price.
* The multipliers.
*/
private final Map<UUID, Double> multipliers = new HashMap<>();
/**
* Create a new item-based price.
*
* @param amount The amount.
* @param item The item.
*/
public PriceItem(final int amount,
@NotNull final TestableItem item) {
this.amountToRemove = Math.max(0, amount);
this(MathContext.EMPTY, ctx -> (double) amount, item);
}
/**
* Create a new item-based price.
*
* @param baseContext The base MathContext.
* @param function The function to get the amount of items to remove.
* @param item The item.
*/
public PriceItem(@NotNull final MathContext baseContext,
@NotNull final Function<MathContext, Double> function,
@NotNull final TestableItem item) {
this.baseContext = baseContext;
this.function = function;
this.item = item;
}
@@ -43,8 +75,10 @@ public final class PriceItem implements Price {
}
@Override
public boolean canAfford(@NotNull Player player) {
if (amountToRemove == 0) {
public boolean canAfford(@NotNull final Player player,
final double multiplier) {
int toRemove = (int) getValue(player, multiplier);
if (toRemove <= 0) {
return true;
}
@@ -56,26 +90,28 @@ public final class PriceItem implements Price {
}
}
return count >= amountToRemove;
return count >= toRemove;
}
@Override
public void pay(@NotNull Player player) {
public void pay(@NotNull final Player player,
final double multiplier) {
int toRemove = (int) getValue(player, multiplier);
int count = 0;
for (ItemStack itemStack : player.getInventory().getContents()) {
if (count >= amountToRemove) {
if (count >= toRemove) {
break;
}
if (item.matches(itemStack)) {
int itemAmount = itemStack.getAmount();
if (itemAmount > amountToRemove) {
itemStack.setAmount(itemAmount - amountToRemove);
if (itemAmount > toRemove) {
itemStack.setAmount(itemAmount - toRemove);
}
if (itemAmount <= amountToRemove) {
if (itemAmount <= toRemove) {
itemStack.setAmount(0);
itemStack.setType(Material.AIR);
}
@@ -84,4 +120,41 @@ public final class PriceItem implements Price {
}
}
}
@Override
public void giveTo(@NotNull final Player player,
final double multiplier) {
ItemStack itemStack = item.getItem().clone();
itemStack.setAmount((int) getValue(player, multiplier));
new DropQueue(player)
.addItem(itemStack)
.forceTelekinesis()
.push();
}
@Override
public double getValue(@NotNull final Player player,
final double multiplier) {
return Math.toIntExact(Math.round(
this.function.apply(MathContext.copyWithPlayer(baseContext, player))
* getMultiplier(player) * multiplier
));
}
@Override
public double getMultiplier(@NotNull final Player player) {
return this.multipliers.getOrDefault(player.getUniqueId(), 1.0);
}
@Override
public void setMultiplier(@NotNull final Player player,
final double multiplier) {
this.multipliers.put(player.getUniqueId(), multiplier);
}
@Override
public String getIdentifier() {
return "eco:item-" + HashedItem.of(this.item.getItem()).getHash();
}
}

View File

@@ -21,7 +21,8 @@ public final class ProxyConstants {
"v1_17_R1",
"v1_18_R1",
"v1_18_R2",
"v1_19_R1"
"v1_19_R1",
"v1_19_R2"
);
private ProxyConstants() {

View File

@@ -0,0 +1,101 @@
package com.willfp.eco.core.sound;
import com.willfp.eco.core.config.interfaces.Config;
import com.willfp.eco.core.serialization.ConfigDeserializer;
import org.bukkit.Location;
import org.bukkit.Sound;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
/**
* A sound that can be played at a location.
*
* @param sound The sound.
* @param pitch The pitch.
* @param volume The volume.
*/
public record PlayableSound(@NotNull Sound sound,
double pitch,
double volume) {
/**
* The deserializer.
*/
private static final ConfigDeserializer<PlayableSound> DESERIALIZER = new Deserializer();
/**
* Create a sound with a default volume.
*
* @param sound The sound.
* @param pitch The pitch.
*/
public PlayableSound(@NotNull final Sound sound,
final double pitch) {
this(sound, pitch, 1.0);
}
/**
* Play the sound to a player.
*
* @param player The player.
*/
public void playTo(@NotNull final Player player) {
player.playSound(player.getLocation(), sound, (float) volume, (float) pitch);
}
/**
* Play the sound at a location.
*
* @param location The location.
*/
public void playAt(@NotNull final Location location) {
World world = location.getWorld();
if (world == null) {
return;
}
world.playSound(location, sound, (float) volume, (float) pitch);
}
/**
* Parse a playable sound from config.
*
* @param config The config.
* @return The sound, or null if it's invalid.
*/
@Nullable
public static PlayableSound create(@NotNull final Config config) {
return DESERIALIZER.deserialize(config);
}
/**
* The deserializer for {@link PlayableSound}.
*/
private static final class Deserializer implements ConfigDeserializer<PlayableSound> {
@Override
public @Nullable PlayableSound deserialize(@NotNull final Config config) {
if (!config.has("sound")) {
return null;
}
try {
Sound sound = Sound.valueOf(config.getString("sound").toUpperCase());
double pitch = Objects.requireNonNullElse(config.getDoubleOrNull("pitch"), 1.0);
double volume = Objects.requireNonNullElse(config.getDoubleOrNull("volume"), 1.0);
return new PlayableSound(
sound,
pitch,
volume
);
} catch (IllegalArgumentException e) {
return null;
}
}
}
}

View File

@@ -16,6 +16,8 @@ import org.jetbrains.annotations.NotNull;
/**
* Utilities / API methods for item durability.
*/
// Have to suppress casts to ItemMeta because the methods don't exist for some older versions that eco supports.
@SuppressWarnings("RedundantCast")
public final class DurabilityUtils {
/**
* Damage an item in a player's inventory.

View File

@@ -579,7 +579,7 @@ public final class StringUtils {
*
* @param lookup The lookup string.
* @return An array of tokens to be processed.
* @author Shawn (https://stackoverflow.com/questions/70606170/split-a-list-on-spaces-and-group-quoted-characters/70606653#70606653)
* @author Shawn (<a href="https://stackoverflow.com/questions/70606170/split-a-list-on-spaces-and-group-quoted-characters/70606653#70606653">...</a>)
*/
@NotNull
public static String[] parseTokens(@NotNull final String lookup) {

View File

@@ -4,9 +4,14 @@ package com.willfp.eco.core.commands
import com.willfp.eco.core.EcoPlugin
import com.willfp.eco.core.command.CommandBase
import com.willfp.eco.core.command.NotificationException
import com.willfp.eco.core.command.impl.PluginCommand
import com.willfp.eco.core.command.impl.Subcommand
import org.bukkit.Bukkit
import org.bukkit.OfflinePlayer
import org.bukkit.command.CommandSender
import org.bukkit.entity.Player
import java.util.function.Predicate
/**
* Helper class for creating commands with builders.
@@ -140,3 +145,92 @@ fun CommandBase.addSubcommand(
init(command)
return command
}
/**
* Throws an exception containing a langYml key if obj is null.
* <p>The {@link CommandBase#onExecute(CommandSender, List) onExecute } in PluginCommand and Subcommand
* automatically handles sending the message to the sender.</p>
* <br>
* @param key key of notification message in langYml
* @return Returns the object given or throws an exception
* @throws NotificationException exception thrown when null
*/
fun <T> T.notifyNull(key: String): T {
return this ?: throw NotificationException(key)
}
/**
* Throws an exception containing a langYml key if predicate tests false
* <p>The {@link CommandBase#onExecute(CommandSender, List) onExecute } in PluginCommand and Subcommand
* automatically handles sending the message to the sender.</p>
* <br>
* @param predicate predicate to test
* @param key key of notification message in langYml
* @param <T> the generic type of object
* @return Returns the object given or throws an exception
*/
fun <T> T.notifyFalse(predicate: Predicate<T>, key: String): T {
predicate.test(this).notifyFalse(key)
return this
}
/**
* Throws an exception containing a langYml key if condition is false.
* <p>The {@link CommandBase#onExecute(CommandSender, List) onExecute } in PluginCommand and Subcommand
* automatically handles sending the message to the sender.</p>
* <br>
* @param key value in the langYml
* @return Returns the condition given or throws an exception
* @throws NotificationException exception thrown when false
*/
fun Boolean?.notifyFalse(key: String): Boolean {
return if (this == true) true else throw NotificationException(key)
}
/**
* Throws an exception containing a langYml key if Bukkit.getPlayer(playerName) is null.
* <p>The {@link CommandBase#onExecute(CommandSender, List) onExecute } in PluginCommand and Subcommand
* automatically handles sending the message to the sender.</p>
* <br>
* @param key value in the langYml
* @return Returns the player
* @throws NotificationException exception thrown when invalid playerName
*/
fun String?.notifyPlayerRequired(key: String): Player {
return Bukkit.getPlayer(this ?: "") ?: throw NotificationException(key)
}
/**
* Throws an exception containing a langYml key if Bukkit.getPlayer(playerName) is null.
* <p>The {@link CommandBase#onExecute(CommandSender, List) onExecute } in PluginCommand and Subcommand
* automatically handles sending the message to the sender.</p>
* <br>
* @param key value in the langYml
* @return Returns the player
* @throws NotificationException exception thrown when invalid playerName
*/
fun String?.notifyOfflinePlayerRequired(key: String): OfflinePlayer {
@Suppress("DEPRECATION")
val player = Bukkit.getOfflinePlayer(this ?: "")
if (!player.hasPlayedBefore() && !player.isOnline) {
throw NotificationException(key)
}
return player
}
/**
* Throws an exception containing a langYml key if player doesn't have permission.
* <p>The {@link CommandBase#onExecute(CommandSender, List) onExecute } in PluginCommand and Subcommand
* automatically handles sending the message to the sender.</p>
* <br>
* @param permission the permission
* @param key value in the langYml
* @return Returns the player
* @throws NotificationException exception thrown when player doesn't have permission
*/
fun Player?.notifyPermissionRequired(permission: String, key: String): Player {
this ?: throw NotificationException(key)
return this.notifyFalse({ this.hasPermission(permission) }, key)
}

View File

@@ -74,12 +74,15 @@ fun SlotBuilder.onClick(clickType: ClickType, action: (Player, InventoryClickEve
fun SlotBuilder.notCaptiveFor(test: (Player) -> Boolean): SlotBuilder =
this.notCaptiveFor { test(it) }
/** @see SlotBuilder.setCaptiveFilter */
fun SlotBuilder.setCaptiveFilter(test: (Player, Menu, ItemStack?) -> Boolean): SlotBuilder =
this.setCaptiveFilter { a, b, c -> test(a, b, c) }
/**
* @see SlotBuilder.setModifier
* @deprecated Use SlotUpdater instead.
*/
@Deprecated("Use SlotUpdater instead")
@Suppress("DEPRECATION")
fun SlotBuilder.setModifier(action: (Player, Menu, item: ItemStack) -> Unit): SlotBuilder =
this.setUpdater { a, b, c -> c.apply { action(a, b, c) } }
@@ -184,6 +187,10 @@ inline fun <reified T : MenuEvent> MenuBuilder.onEvent(crossinline handler: (Pla
})
}
/** @see MenuBuilder.onBuild */
fun MenuBuilder.onBuild(action: (Menu) -> Unit): MenuBuilder =
this.onBuild { action(it) }
/** Kotlin builder for menus. */
fun menu(
rows: Int,

View File

@@ -2,13 +2,31 @@
package com.willfp.eco.core.integrations.shop
import com.willfp.eco.core.price.Price
import org.bukkit.entity.Player
import org.bukkit.inventory.ItemStack
/** @see ShopManager.getItemPrice **/
/** @see ShopManager.getItemPrice * */
@Deprecated(
"Prices depend on players, this will always return 0.",
level = DeprecationLevel.ERROR,
replaceWith = ReplaceWith("this.getValue(player)")
)
val ItemStack.price: Double
get() = ShopManager.getItemPrice(this)
get() = 0.0
/** @see ShopManager.getItemPrice **/
/** @see ShopManager.getItemPrice * */
@Deprecated(
"Use the price system instead, prices may not be currencies.",
ReplaceWith("this.getValue(player)"),
)
fun ItemStack.getPrice(player: Player): Double =
ShopManager.getItemPrice(this, player)
this.getUnitValue(player).getValue(player, this.amount.toDouble())
/** @see ShopManager.getUnitValue */
fun ItemStack.getUnitValue(player: Player): Price =
ShopManager.getUnitValue(this, player)
/** @see ShopManager.isSellable */
fun ItemStack?.isSellable(player: Player): Boolean =
ShopManager.isSellable(this, player)

View File

@@ -0,0 +1,14 @@
@file:JvmName("ItemBuilderExtensions")
package com.willfp.eco.core.items.builder
import com.willfp.eco.core.items.TestableItem
import org.bukkit.inventory.ItemStack
/** Modify an item with a builder. */
fun TestableItem.modify(builder: ItemBuilder.() -> Unit): ItemStack =
this.item.modify(builder)
/** Modify an item with a builder. */
fun ItemStack.modify(builder: ItemBuilder.() -> Unit): ItemStack =
ItemStackBuilder(this).apply(builder).build()

View File

@@ -0,0 +1,7 @@
@file:JvmName("TestableExtensions")
package com.willfp.eco.core.lookup
/** @see Testable.matches */
fun <T> T?.matches(test: Testable<T>) =
test.matches(this)

View File

@@ -0,0 +1,28 @@
package com.willfp.eco.internal.command
import org.bukkit.command.Command
import org.bukkit.command.CommandSender
import org.bukkit.command.PluginIdentifiableCommand
import org.bukkit.command.TabCompleter
class DelegatedBukkitCommand(
private val delegate: EcoPluginCommand
) : Command(delegate.name), TabCompleter, PluginIdentifiableCommand {
override fun execute(sender: CommandSender, label: String, args: Array<out String>?): Boolean {
return false
}
override fun onTabComplete(
sender: CommandSender,
command: Command,
label: String,
args: Array<out String>?
): List<String> {
return mutableListOf() // Mutable in case bukkit requires this (I haven't checked.)
}
override fun getPlugin() = delegate.plugin
override fun getPermission() = delegate.permission
override fun getDescription() = delegate.description ?: ""
override fun getAliases(): List<String> = delegate.aliases
}

View File

@@ -0,0 +1,52 @@
package com.willfp.eco.internal.command
import com.willfp.eco.core.Eco
import com.willfp.eco.core.EcoPlugin
import com.willfp.eco.core.command.CommandBase
import com.willfp.eco.core.command.PluginCommandBase
import org.bukkit.Bukkit
class EcoPluginCommand(
parentDelegate: CommandBase,
plugin: EcoPlugin,
name: String,
permission: String,
playersOnly: Boolean
) : HandledCommand(parentDelegate, plugin, name, permission, playersOnly),
PluginCommandBase {
override fun register() {
val command = Bukkit.getPluginCommand(name)
if (command == null) {
unregister()
commandMap.register(plugin.name.lowercase(), DelegatedBukkitCommand(this))
} else {
command.setExecutor(this)
description?.let {
command.setDescription(it)
}
if (aliases.isNotEmpty()) {
command.aliases = aliases
}
}
}
override fun unregister() {
val found = commandMap.getCommand(name)
found?.unregister(commandMap)
Eco.get().syncCommands()
}
}
class EcoSubcommand(
parentDelegate: CommandBase,
plugin: EcoPlugin,
name: String,
permission: String,
playersOnly: Boolean
) : HandledCommand(parentDelegate, plugin, name, permission, playersOnly)

View File

@@ -0,0 +1,183 @@
package com.willfp.eco.internal.command
import com.willfp.eco.core.EcoPlugin
import com.willfp.eco.core.command.CommandBase
import com.willfp.eco.core.command.NotificationException
import com.willfp.eco.core.config.base.LangYml
import org.bukkit.Bukkit
import org.bukkit.command.Command
import org.bukkit.command.CommandExecutor
import org.bukkit.command.CommandMap
import org.bukkit.command.CommandSender
import org.bukkit.command.TabCompleter
import org.bukkit.entity.Player
import org.bukkit.util.StringUtil
/**
* Abstract class for commands that can be handled.
* <p>
* Handled commands have a method to pass in raw input from bukkit commands
* in order to execute the command-specific code.
*/
abstract class HandledCommand(
private val parentDelegate: CommandBase,
private val plugin: EcoPlugin,
private val name: String,
private val permission: String,
private val playersOnly: Boolean
) : CommandBase, CommandExecutor, TabCompleter {
private val subcommands = mutableListOf<CommandBase>()
override fun onCommand(
sender: CommandSender,
command: Command,
label: String,
args: Array<out String>?
): Boolean {
if (!command.name.equals(name, true)) {
return false
}
if (args != null) {
handleExecution(sender, args.toList())
}
return true
}
override fun onTabComplete(
sender: CommandSender,
command: Command,
label: String,
args: Array<out String>?
): MutableList<String>? {
return handleTabComplete(sender, args?.toList() ?: listOf()).toMutableList()
}
override fun getPlugin() = this.plugin
override fun getName() = this.name
override fun getPermission() = this.permission
override fun isPlayersOnly() = this.playersOnly
override fun getSubcommands() = this.subcommands
override fun getWrapped() = this.parentDelegate
override fun addSubcommand(command: CommandBase): CommandBase {
subcommands.add(command)
return this
}
override fun onExecute(sender: CommandSender, args: List<String>) = parentDelegate.onExecute(sender, args)
override fun onExecute(sender: Player, args: List<String>) = parentDelegate.onExecute(sender, args)
override fun tabComplete(sender: CommandSender, args: List<String>): List<String> =
parentDelegate.tabComplete(sender, args)
override fun tabComplete(sender: Player, args: List<String>): List<String> =
parentDelegate.tabComplete(sender, args)
/**
* Handle the command.
*
* @param sender The sender.
* @param args The arguments.
*/
private fun CommandBase.handleExecution(sender: CommandSender, args: List<String>) {
if (!sender.canExecute(this, plugin)) {
return
}
if (args.isNotEmpty()) {
for (subcommand in subcommands) {
if (subcommand.name.equals(args[0], true)) {
if (!sender.canExecute(subcommand, plugin)) {
return
}
subcommand.handleExecution(sender, args.subList(1, args.size))
return
}
}
}
try {
notifyFalse(!isPlayersOnly || sender is Player, LangYml.KEY_NOT_PLAYER)
onExecute(sender, args)
if (sender is Player) {
onExecute(sender, args)
}
} catch (e: NotificationException) {
sender.sendMessage(plugin.langYml.getMessage(e.key))
return
}
}
/**
* Handle the tab completion.
*
* @param sender The sender.
* @param args The arguments.
* @return The tab completion results.
*/
private fun CommandBase.handleTabComplete(sender: CommandSender, args: List<String>): List<String> {
if (!sender.hasPermission(permission)) return emptyList()
if (args.size == 1) {
val completions = subcommands.filter { sender.hasPermission(it.permission) }.map { it.name }
val list = mutableListOf<String>()
StringUtil.copyPartialMatches(args[0], completions, list)
if (completions.isNotEmpty()) {
return completions
}
}
if (args.size >= 2) {
val matchingCommand =
subcommands.firstOrNull {
sender.hasPermission(it.permission) && it.name.equals(args[0], true)
}
if (matchingCommand != null) {
val completions = matchingCommand.handleTabComplete(sender, args.subList(1, args.size)).toMutableList()
if (sender is Player) {
completions.addAll(matchingCommand.handleTabComplete(sender, args.subList(1, args.size)))
}
return completions
}
}
val completions = tabComplete(sender, args).toMutableList()
if (sender is Player) {
completions.addAll(tabComplete(sender, args))
}
return completions.sorted()
}
}
val commandMap: CommandMap
get() = try {
val fCommandMap = Bukkit.getServer().javaClass.getDeclaredField("commandMap")
fCommandMap.trySetAccessible()
fCommandMap.get(Bukkit.getServer()) as CommandMap
} catch (e: Exception) {
throw NullPointerException("Command map wasn't found!")
}
fun CommandSender.canExecute(command: CommandBase, plugin: EcoPlugin): Boolean {
if (!this.hasPermission(command.permission) && this is Player) {
this.sendMessage(plugin.langYml.noPermission)
return false
}
return true
}

View File

@@ -77,7 +77,7 @@ private abstract class ConfigTypeHandler(
val type: ConfigType
) {
fun toMap(input: String?): Map<String, Any?> {
if (input == null || input.isBlank()) {
if (input.isNullOrBlank()) {
return emptyMap()
}

View File

@@ -3,7 +3,6 @@ package com.willfp.eco.internal.config
import com.willfp.eco.core.config.ConfigType
import com.willfp.eco.core.placeholder.InjectablePlaceholder
@Suppress("UNCHECKED_CAST")
class EcoConfigSection(
type: ConfigType,
values: Map<String, Any?> = emptyMap(),

View File

@@ -3,6 +3,7 @@ package com.willfp.eco.internal.gui
import com.willfp.eco.core.gui.menu.Menu
import org.bukkit.entity.Player
// To check in the future, I'm not sure if this is necessary functionality at all.
class MergedStateMenu(
private val base: Menu,
private val additional: Menu

View File

@@ -39,7 +39,7 @@ class EcoMenu(
override fun getSlot(row: Int, column: Int): Slot =
getPossiblyReactiveSlot(row, column, null)
override fun getSlot(row: Int, column: Int, player: Player, menu: Menu): Slot =
override fun getSlot(row: Int, column: Int, player: Player): Slot =
getPossiblyReactiveSlot(row, column, player)
override fun open(player: Player): Inventory {

View File

@@ -22,6 +22,7 @@ class EcoMenuBuilder(
private val onOpen = mutableListOf<OpenHandler>()
private val onRender = mutableListOf<(Player, Menu) -> Unit>()
private val menuEventHandlers = mutableListOf<MenuEventHandler<*>>()
private val onBuild = mutableListOf<(Menu) -> Unit>()
private var allowsChangingHeldItem = false
override fun getRows() = rows
@@ -80,6 +81,11 @@ class EcoMenuBuilder(
return this
}
override fun onBuild(action: Consumer<Menu>): MenuBuilder {
onBuild += { action.accept(it) }
return this
}
override fun allowChangingHeldItem(): MenuBuilder {
allowsChangingHeldItem = true
return this
@@ -122,7 +128,7 @@ class EcoMenuBuilder(
}
}
return EcoMenu(
val menu = EcoMenu(
rows,
columns,
layeredComponents,
@@ -133,5 +139,9 @@ class EcoMenuBuilder(
menuEventHandlers,
allowsChangingHeldItem
)
onBuild.forEach { it(menu) } // Run on build functions.
return menu
}
}

View File

@@ -1,8 +1,10 @@
package com.willfp.eco.internal.gui.menu
import com.willfp.eco.core.gui.menu.events.CaptiveItemChangeEvent
import com.willfp.eco.core.items.isEmpty
import com.willfp.eco.core.recipe.parts.EmptyTestableItem
import com.willfp.eco.util.MenuUtils
import com.willfp.eco.util.openMenu
import org.bukkit.entity.Player
import org.bukkit.inventory.Inventory
import org.bukkit.inventory.ItemStack
@@ -31,16 +33,22 @@ class RenderedInventory(
val state = mutableMapOf<String, Any?>()
fun render() {
val previousCaptive = captiveItems.toMap()
// This can happen when opening menus from other menus,
// fixing a bug where multiple paginated menus on top of
// each other caused bugs with page changer display.
if (this.menu != player.openMenu) {
MenuHandler.unregisterInventory(this.inventory)
return
}
captiveItems.clear()
val newCaptive = mutableMapOf<GUIPosition, ItemStack>()
for (row in (1..menu.rows)) {
for (column in (1..menu.columns)) {
val position = GUIPosition(row, column)
val bukkit = MenuUtils.rowColumnToSlot(row, column, menu.columns)
val slot = menu.getSlot(row, column, player, menu)
val slot = menu.getSlot(row, column, player)
val renderedItem = slot.getItemStack(player)
if (slot.isCaptive(player, menu)) {
@@ -48,11 +56,11 @@ class RenderedInventory(
if (slot.isCaptiveFromEmpty) {
if (!actualItem.isEmpty) {
captiveItems[position] = actualItem
newCaptive[position] = actualItem
}
} else {
if (actualItem != renderedItem && !EmptyTestableItem().matches(actualItem)) {
captiveItems[position] = actualItem
newCaptive[position] = actualItem
}
}
} else {
@@ -61,6 +69,24 @@ class RenderedInventory(
}
}
val previousCaptive = captiveItems.toMap()
captiveItems.clear()
captiveItems.putAll(newCaptive)
// Call captive item change event
for (position in previousCaptive.keys union newCaptive.keys) {
if (previousCaptive[position] != newCaptive[position]) {
menu.callEvent(
player, CaptiveItemChangeEvent(
position.row,
position.column,
previousCaptive[position],
newCaptive[position]
)
)
}
}
menu.runOnRender(player)
// Run second render if captive items changed
@@ -69,7 +95,7 @@ class RenderedInventory(
for (column in (1..menu.columns)) {
val bukkit = MenuUtils.rowColumnToSlot(row, column, menu.columns)
val slot = menu.getSlot(row, column, player, menu)
val slot = menu.getSlot(row, column, player)
val renderedItem = slot.getItemStack(player)
if (!slot.isCaptive(player, menu)) {
@@ -87,7 +113,7 @@ class RenderedInventory(
for (column in (1..menu.columns)) {
val bukkit = MenuUtils.rowColumnToSlot(row, column, menu.columns)
val slot = menu.getSlot(row, column, player, menu)
val slot = menu.getSlot(row, column, player)
if (slot.isCaptive(player, menu)) {
inventory.setItem(bukkit, slot.getItemStack(player))

View File

@@ -1,20 +1,24 @@
package com.willfp.eco.internal.gui.slot
import com.willfp.eco.core.gui.menu.Menu
import com.willfp.eco.core.gui.slot.functional.CaptiveFilter
import com.willfp.eco.core.gui.slot.functional.SlotHandler
import com.willfp.eco.core.gui.slot.functional.SlotProvider
import com.willfp.eco.util.toSingletonList
import org.bukkit.Material
import org.bukkit.entity.Player
import org.bukkit.event.inventory.ClickType
import org.bukkit.inventory.ItemStack
class EcoCaptiveSlot(
provider: SlotProvider,
private val captiveFromEmpty: Boolean,
private val notCaptiveFor: (Player) -> Boolean
private val notCaptiveFor: (Player) -> Boolean,
private val filter: CaptiveFilter
) : EcoSlot(
provider,
ClickType.values().associateWith {
captiveWithTest(notCaptiveFor).toSingletonList()
captiveWithTest(notCaptiveFor, filter).toSingletonList()
},
{ _, _, prev -> prev }
) {
@@ -22,13 +26,25 @@ class EcoCaptiveSlot(
return !notCaptiveFor(player)
}
override fun isAllowedCaptive(player: Player, menu: Menu, itemStack: ItemStack?): Boolean {
return filter.isAllowed(player, menu, itemStack)
}
override fun isCaptiveFromEmpty(): Boolean {
return captiveFromEmpty
}
}
private fun captiveWithTest(test: (Player) -> Boolean): SlotHandler {
return SlotHandler { event, _, _ ->
event.isCancelled = test(event.whoClicked as Player)
}
private fun captiveWithTest(
notCaptiveFor: (Player) -> Boolean,
filter: CaptiveFilter
): SlotHandler = SlotHandler { event, _, menu ->
val player = event.whoClicked as Player
val item = event.currentItem.nullIfAir() ?: event.cursor.nullIfAir()
event.isCancelled = !filter.isAllowed(player, menu, item) || notCaptiveFor(player)
}
private fun ItemStack?.nullIfAir(): ItemStack? =
if (this?.type == Material.AIR) null else this

View File

@@ -33,9 +33,5 @@ open class EcoSlot(
return updater.update(player, menu, base) ?: return ItemStack(Material.AIR)
}
override fun isCaptive(player: Player, menu: Menu): Boolean {
return false
}
override fun getActionableSlot(player: Player, menu: Menu): EcoSlot = this
}

View File

@@ -2,6 +2,7 @@ package com.willfp.eco.internal.gui.slot
import com.willfp.eco.core.gui.slot.Slot
import com.willfp.eco.core.gui.slot.SlotBuilder
import com.willfp.eco.core.gui.slot.functional.CaptiveFilter
import com.willfp.eco.core.gui.slot.functional.SlotHandler
import com.willfp.eco.core.gui.slot.functional.SlotProvider
import com.willfp.eco.core.gui.slot.functional.SlotUpdater
@@ -16,7 +17,9 @@ class EcoSlotBuilder(private val provider: SlotProvider) : SlotBuilder {
private val handlers = mutableMapOf<ClickType, MutableList<SlotHandler>>()
private var notCaptiveFor: (Player) -> Boolean = { false }
private var captiveFilter =
CaptiveFilter { _, _, _ -> true }
private var notCaptiveFor: (Player) -> Boolean = { _ -> false}
override fun onClick(type: ClickType, action: SlotHandler): SlotBuilder {
handlers.computeIfAbsent(type) { mutableListOf() } += action
@@ -24,7 +27,12 @@ class EcoSlotBuilder(private val provider: SlotProvider) : SlotBuilder {
}
override fun notCaptiveFor(predicate: Predicate<Player>): SlotBuilder {
notCaptiveFor = { predicate.test(it) }
notCaptiveFor = { player -> predicate.test(player) }
return this
}
override fun setCaptiveFilter(filter: CaptiveFilter): SlotBuilder {
captiveFilter = filter
return this
}
@@ -44,7 +52,8 @@ class EcoSlotBuilder(private val provider: SlotProvider) : SlotBuilder {
EcoCaptiveSlot(
provider,
captiveFromEmpty,
notCaptiveFor
notCaptiveFor,
captiveFilter
)
} else {
EcoSlot(

View File

@@ -21,7 +21,8 @@ object ArgParserName : LookupArgParser {
val formatted = StringUtils.format(name)
@Suppress("UsePropertyAccessSyntax")
// I don't know why it says it's redundant, the compiler yells at me
@Suppress("UsePropertyAccessSyntax", "RedundantSuppression")
meta.setDisplayName(formatted)
return Predicate {

View File

@@ -1,8 +1,10 @@
package com.willfp.eco.internal.price
import com.willfp.eco.core.math.MathContext
import com.willfp.eco.core.price.Price
import com.willfp.eco.core.price.PriceFactory
import com.willfp.eco.core.price.impl.PriceEconomy
import java.util.function.Function
object PriceFactoryEconomy : PriceFactory {
override fun getNames() = listOf(
@@ -10,5 +12,7 @@ object PriceFactoryEconomy : PriceFactory {
"$"
)
override fun create(value: Double): Price = PriceEconomy(value)
override fun create(baseContext: MathContext, function: Function<MathContext, Double>): Price {
return PriceEconomy(baseContext, function)
}
}

View File

@@ -1,8 +1,11 @@
package com.willfp.eco.internal.price
import com.willfp.eco.core.math.MathContext
import com.willfp.eco.core.price.Price
import com.willfp.eco.core.price.PriceFactory
import org.bukkit.entity.Player
import java.util.UUID
import java.util.function.Function
import kotlin.math.roundToInt
object PriceFactoryXP : PriceFactory {
@@ -12,15 +15,41 @@ object PriceFactoryXP : PriceFactory {
"experience"
)
override fun create(value: Double): Price = PriceXP(value.roundToInt())
override fun create(baseContext: MathContext, function: Function<MathContext, Double>): Price {
return PriceXP(baseContext) { function.apply(it).roundToInt() }
}
private class PriceXP(
private val xp: Int
private val baseContext: MathContext,
private val xp: (MathContext) -> Int
) : Price {
override fun canAfford(player: Player) = player.totalExperience >= xp
private val multipliers = mutableMapOf<UUID, Double>()
override fun pay(player: Player) {
player.totalExperience -= xp
override fun canAfford(player: Player, multiplier: Double): Boolean =
player.totalExperience >= getValue(player, multiplier)
override fun pay(player: Player, multiplier: Double) {
player.totalExperience -= getValue(player, multiplier).roundToInt()
}
override fun giveTo(player: Player, multiplier: Double) {
player.totalExperience += getValue(player, multiplier).roundToInt()
}
override fun getValue(player: Player, multiplier: Double): Double {
return xp(MathContext.copyWithPlayer(baseContext, player)) * getMultiplier(player) * multiplier
}
override fun getMultiplier(player: Player): Double {
return multipliers[player.uniqueId] ?: 1.0
}
override fun setMultiplier(player: Player, multiplier: Double) {
multipliers[player.uniqueId] = multiplier.roundToInt().toDouble()
}
override fun getIdentifier(): String {
return "eco:xp"
}
}
}

View File

@@ -1,8 +1,11 @@
package com.willfp.eco.internal.price
import com.willfp.eco.core.math.MathContext
import com.willfp.eco.core.price.Price
import com.willfp.eco.core.price.PriceFactory
import org.bukkit.entity.Player
import java.util.UUID
import java.util.function.Function
import kotlin.math.roundToInt
object PriceFactoryXPLevels : PriceFactory {
@@ -13,15 +16,40 @@ object PriceFactoryXPLevels : PriceFactory {
"explevels",
)
override fun create(value: Double): Price = PriceXPLevel(value.roundToInt())
override fun create(baseContext: MathContext, function: Function<MathContext, Double>): Price {
return PriceXPLevel(baseContext) { function.apply(it).roundToInt() }
}
private class PriceXPLevel(
private val levels: Int
private val baseContext: MathContext,
private val level: (MathContext) -> Int
) : Price {
override fun canAfford(player: Player) = player.level >= levels
private val multipliers = mutableMapOf<UUID, Double>()
override fun pay(player: Player) {
player.level -= levels
override fun canAfford(player: Player, multiplier: Double) = player.level >= getValue(player, multiplier)
override fun pay(player: Player, multiplier: Double) {
player.level -= getValue(player, multiplier).roundToInt()
}
override fun giveTo(player: Player, multiplier: Double) {
player.level += getValue(player, multiplier).roundToInt()
}
override fun getValue(player: Player, multiplier: Double): Double {
return level(MathContext.copyWithPlayer(baseContext, player)) * getMultiplier(player) * multiplier
}
override fun getMultiplier(player: Player): Double {
return multipliers[player.uniqueId] ?: 1.0
}
override fun setMultiplier(player: Player, multiplier: Double) {
multipliers[player.uniqueId] = multiplier.roundToInt().toDouble()
}
override fun getIdentifier(): String {
return "eco:xp-levels"
}
}
}

View File

@@ -4,7 +4,6 @@ import com.willfp.eco.core.entities.ai.EntityGoal
import com.willfp.eco.core.entities.ai.TargetGoal
import com.willfp.eco.internal.spigot.proxy.common.ai.EntityGoalFactory
import com.willfp.eco.internal.spigot.proxy.common.ai.TargetGoalFactory
import net.minecraft.core.Registry
import net.minecraft.nbt.CompoundTag
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.LivingEntity
@@ -48,17 +47,14 @@ private val MATERIAL_TO_ITEM = mutableMapOf<Material, Item>()
fun Material.toItem(): Item =
MATERIAL_TO_ITEM.getOrPut(this) {
Registry.ITEM.getOptional(this.key.toResourceLocation())
.orElseThrow { IllegalArgumentException("Material is not item!") }
impl.materialToItem(this)
}
private val ITEM_TO_MATERIAL = mutableMapOf<Item, Material>()
fun Item.toMaterial(): Material =
ITEM_TO_MATERIAL.getOrPut(this) {
val material = Material.getMaterial(Registry.ITEM.getKey(this).path.uppercase())
?: throw IllegalArgumentException("Invalid material!")
material
impl.itemToMaterial(this)
}
fun CompoundTag.makePdc(base: Boolean = false): PersistentDataContainer =
@@ -94,6 +90,10 @@ interface CommonsProvider {
return null
}
fun materialToItem(material: Material): Item
fun itemToMaterial(item: Item): Material
companion object {
fun setIfNeeded(provider: CommonsProvider) {
if (::impl.isInitialized) {

View File

@@ -1,21 +1,23 @@
package com.willfp.eco.internal.spigot.proxy.common.ai.entity
import com.willfp.eco.core.entities.ai.entity.EntityGoalLeapAtTarget
import com.willfp.eco.core.entities.ai.entity.EntityGoalIllusionerBlindnessSpell
import com.willfp.eco.internal.spigot.proxy.common.ai.EntityGoalFactory
import com.willfp.eco.internal.spigot.proxy.common.ai.opengoals.IllusionerBlindnessSpellGoal
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.goal.Goal
import net.minecraft.world.entity.monster.Illusioner
object IllusionerBlindnessSpellGoalFactory : EntityGoalFactory<EntityGoalLeapAtTarget> {
override fun create(apiGoal: EntityGoalLeapAtTarget, entity: PathfinderMob): Goal? {
object IllusionerBlindnessSpellGoalFactory : EntityGoalFactory<EntityGoalIllusionerBlindnessSpell> {
override fun create(apiGoal: EntityGoalIllusionerBlindnessSpell, entity: PathfinderMob): Goal? {
if (entity !is Illusioner) return null
return IllusionerBlindnessSpellGoal(
entity
)
// Have to use reflection for it to work
return Illusioner::class.java.declaredClasses[1]
.getDeclaredConstructor(Illusioner::class.java)
.apply { isAccessible = true }
.newInstance(entity) as Goal
}
override fun isGoalOfType(goal: Goal) = goal is IllusionerBlindnessSpellGoal
|| goal::class.java.name.contains("IllusionerBlindnessSpellGoal")
override fun isGoalOfType(goal: Goal): Boolean {
return Illusioner::class.java.declaredClasses[1].isInstance(goal)
}
}

View File

@@ -1,21 +1,23 @@
package com.willfp.eco.internal.spigot.proxy.common.ai.entity
import com.willfp.eco.core.entities.ai.entity.EntityGoalLeapAtTarget
import com.willfp.eco.core.entities.ai.entity.EntityGoalIllusionerMirrorSpell
import com.willfp.eco.internal.spigot.proxy.common.ai.EntityGoalFactory
import com.willfp.eco.internal.spigot.proxy.common.ai.opengoals.IllusionerMirrorSpellGoal
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.goal.Goal
import net.minecraft.world.entity.monster.Illusioner
object IllusionerMirrorSpellGoalFactory : EntityGoalFactory<EntityGoalLeapAtTarget> {
override fun create(apiGoal: EntityGoalLeapAtTarget, entity: PathfinderMob): Goal? {
object IllusionerMirrorSpellGoalFactory : EntityGoalFactory<EntityGoalIllusionerMirrorSpell> {
override fun create(apiGoal: EntityGoalIllusionerMirrorSpell, entity: PathfinderMob): Goal? {
if (entity !is Illusioner) return null
return IllusionerMirrorSpellGoal(
entity
)
// Have to use reflection for it to work
return Illusioner::class.java.declaredClasses[0]
.getDeclaredConstructor(Illusioner::class.java)
.apply { isAccessible = true }
.newInstance(entity) as Goal
}
override fun isGoalOfType(goal: Goal) = goal is IllusionerMirrorSpellGoal
|| goal::class.java.name.contains("IllusionerMirrorSpellGoal")
override fun isGoalOfType(goal: Goal): Boolean {
return Illusioner::class.java.declaredClasses[0].isInstance(goal)
}
}

View File

@@ -39,7 +39,6 @@ private class EnhancedInteractGoal(
return if (mob.random.nextFloat() >= probability) {
false
} else {
@Suppress("SENSELESS_COMPARISON")
if (mob.target != null) {
lookAt = mob.target
}

View File

@@ -1,50 +0,0 @@
package com.willfp.eco.internal.spigot.proxy.common.ai.opengoals
import net.minecraft.sounds.SoundEvent
import net.minecraft.sounds.SoundEvents
import net.minecraft.world.Difficulty
import net.minecraft.world.effect.MobEffectInstance
import net.minecraft.world.effect.MobEffects
import net.minecraft.world.entity.monster.Illusioner
import net.minecraft.world.entity.monster.SpellcasterIllager
import org.bukkit.event.entity.EntityPotionEffectEvent
class IllusionerBlindnessSpellGoal(
private val illusioner: Illusioner
) : OpenUseSpellGoal(illusioner) {
override val castingInterval = 180
override val castingTime = 20
override val spellPrepareSound: SoundEvent = SoundEvents.ILLUSIONER_PREPARE_BLINDNESS
override val spell: SpellcasterIllager.IllagerSpell = SpellcasterIllager.IllagerSpell.BLINDNESS
private var lastTargetId = 0
override fun canUse(): Boolean {
return if (super.canUse()) {
false
} else if (illusioner.target == null) {
false
} else if (illusioner.target!!.id == lastTargetId) {
false
} else {
illusioner.level.getCurrentDifficultyAt(illusioner.blockPosition()).isHarderThan(
Difficulty.NORMAL.ordinal.toFloat()
)
}
}
override fun start() {
super.start()
if (illusioner.target != null) {
lastTargetId = illusioner.target!!.id
}
}
override fun performSpellCasting() {
illusioner.target?.addEffect(
MobEffectInstance(MobEffects.BLINDNESS, 400),
illusioner,
EntityPotionEffectEvent.Cause.ATTACK
) // CraftBukkit
}
}

View File

@@ -1,29 +0,0 @@
package com.willfp.eco.internal.spigot.proxy.common.ai.opengoals
import net.minecraft.sounds.SoundEvent
import net.minecraft.sounds.SoundEvents
import net.minecraft.world.effect.MobEffectInstance
import net.minecraft.world.effect.MobEffects
import net.minecraft.world.entity.monster.Illusioner
import net.minecraft.world.entity.monster.SpellcasterIllager
import org.bukkit.event.entity.EntityPotionEffectEvent
class IllusionerMirrorSpellGoal(
private val illusioner: Illusioner
) : OpenUseSpellGoal(illusioner) {
override val castingInterval = 340
override val castingTime = 20
override val spellPrepareSound: SoundEvent = SoundEvents.ILLUSIONER_PREPARE_MIRROR
override val spell: SpellcasterIllager.IllagerSpell = SpellcasterIllager.IllagerSpell.DISAPPEAR
override fun canUse(): Boolean {
return if (!super.canUse()) false else !illusioner.hasEffect(MobEffects.INVISIBILITY)
}
override fun performSpellCasting() {
illusioner.addEffect(
MobEffectInstance(MobEffects.INVISIBILITY, 1200),
EntityPotionEffectEvent.Cause.ILLUSION
)
}
}

View File

@@ -1,79 +0,0 @@
package com.willfp.eco.internal.spigot.proxy.common.ai.opengoals
import net.minecraft.sounds.SoundEvent
import net.minecraft.world.entity.EntityType
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.goal.Goal
import net.minecraft.world.entity.monster.SpellcasterIllager
@Suppress("UNCHECKED_CAST")
class DelegatedSpellcaster(private val handle: SpellcasterIllager) : SpellcasterIllager(
handle.type as EntityType<out SpellcasterIllager>,
handle.level
) {
var openSpellCastingTickCount
get() = this.spellCastingTickCount
set(value) {
this.spellCastingTickCount = value
}
val openCastingSoundEvent = this.castingSoundEvent
override fun applyRaidBuffs(wave: Int, unused: Boolean) {
handle.applyRaidBuffs(wave, unused)
}
override fun getCelebrateSound(): SoundEvent {
return handle.celebrateSound
}
override fun getCastingSoundEvent(): SoundEvent {
return this.openCastingSoundEvent
}
}
@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
abstract class OpenUseSpellGoal(
private val handle: SpellcasterIllager
) : Goal() {
private var attackWarmupDelay = 0
private var nextAttackTickCount = 0
private val openHandle = DelegatedSpellcaster(handle)
override fun canUse(): Boolean {
val entityliving: LivingEntity = handle.target ?: return false
return if (entityliving.isAlive) if (handle.isCastingSpell) false else handle.tickCount >= nextAttackTickCount else false
}
override fun canContinueToUse(): Boolean {
val entityliving: LivingEntity = handle.target ?: return false
return entityliving.isAlive && attackWarmupDelay > 0
}
override fun start() {
attackWarmupDelay = castWarmupTime
openHandle.openSpellCastingTickCount = castingTime
nextAttackTickCount = handle.tickCount + castingInterval
val soundeffect = spellPrepareSound
if (soundeffect != null) {
handle.playSound(soundeffect, 1.0f, 1.0f)
}
handle.setIsCastingSpell(spell)
}
override fun tick() {
--attackWarmupDelay
if (attackWarmupDelay == 0) {
performSpellCasting()
handle.playSound(openHandle.openCastingSoundEvent, 1.0f, 1.0f)
}
}
protected abstract fun performSpellCasting()
protected abstract val castingTime: Int
protected abstract val castingInterval: Int
protected abstract val spellPrepareSound: SoundEvent?
protected abstract val spell: SpellcasterIllager.IllagerSpell?
protected open val castWarmupTime: Int = 60
}

View File

@@ -32,7 +32,6 @@ private class EnhancedHurtByTargetGoal(
return false
}
@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
return super.canAttack(target, targetPredicate)
}
}

View File

@@ -192,7 +192,7 @@ class EcoFastItemStack(
apply()
}
@Suppress("UNNECESSARY_NOT_NULL_ASSERTION")
@Suppress("RedundantSuppression", "UNNECESSARY_NOT_NULL_ASSERTION")
private var flagBits: Byte
get() =
if (handle.hasTag() && handle.getTag()!!.contains(
@@ -255,7 +255,7 @@ class EcoFastItemStack(
}
override fun hashCode(): Int {
@Suppress("UNNECESSARY_SAFE_CALL")
@Suppress("RedundantSuppression", "UNNECESSARY_SAFE_CALL")
return handle.getTag()?.hashCode() ?: (0b00010101 * 31 + Item.getId(handle.getItem()))
}

View File

@@ -0,0 +1,35 @@
package com.willfp.eco.internal.spigot.proxy.v1_17_R1
import com.willfp.eco.core.command.impl.PluginCommand
import com.willfp.eco.internal.spigot.proxy.BukkitCommandsProxy
import org.bukkit.Bukkit
import org.bukkit.command.Command
import org.bukkit.command.SimpleCommandMap
import org.bukkit.craftbukkit.v1_17_R1.CraftServer
import java.lang.reflect.Field
class BukkitCommands : BukkitCommandsProxy {
private val knownCommandsField: Field by lazy {
SimpleCommandMap::class.java.getDeclaredField("knownCommands")
.apply {
isAccessible = true
}
}
@Suppress("UNCHECKED_CAST")
private val knownCommands: MutableMap<String, Command>
get() = knownCommandsField.get(getCommandMap()) as MutableMap<String, Command>
override fun getCommandMap(): SimpleCommandMap {
return (Bukkit.getServer() as CraftServer).commandMap
}
override fun syncCommands() {
(Bukkit.getServer() as CraftServer).syncCommands()
}
override fun unregisterCommand(command: PluginCommand) {
knownCommands.remove(command.name)
knownCommands.remove("${command.plugin.name.lowercase()}:${command.name}")
}
}

View File

@@ -2,11 +2,15 @@ package com.willfp.eco.internal.spigot.proxy.v1_17_R1
import com.willfp.eco.internal.spigot.proxy.CommonsInitializerProxy
import com.willfp.eco.internal.spigot.proxy.common.CommonsProvider
import com.willfp.eco.internal.spigot.proxy.common.toResourceLocation
import net.minecraft.core.Registry
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.Tag
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.item.Item
import org.bukkit.Bukkit
import org.bukkit.Material
import org.bukkit.NamespacedKey
import org.bukkit.craftbukkit.v1_17_R1.CraftServer
import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity
@@ -131,5 +135,13 @@ class CommonsInitializer : CommonsInitializerProxy {
}
}
}
override fun materialToItem(material: Material): Item =
Registry.ITEM.getOptional(material.key.toResourceLocation())
.orElseThrow { IllegalArgumentException("Material is not item!") }
override fun itemToMaterial(item: Item) =
Material.getMaterial(Registry.ITEM.getKey(item).path.uppercase())
?: throw IllegalArgumentException("Invalid material!")
}
}

View File

@@ -12,7 +12,6 @@ import org.bukkit.persistence.PersistentDataContainer
import org.bukkit.persistence.PersistentDataType
class ExtendedPersistentDataContainerFactory : ExtendedPersistentDataContainerFactoryProxy {
@Suppress("UNCHECKED_CAST")
private val registry: CraftPersistentDataTypeRegistry
init {
@@ -38,7 +37,7 @@ class ExtendedPersistentDataContainerFactory : ExtendedPersistentDataContainerFa
}
inner class EcoPersistentDataContainer(
val handle: CraftPersistentDataContainer
private val handle: CraftPersistentDataContainer
) : ExtendedPersistentDataContainer {
@Suppress("UNCHECKED_CAST")
private val customDataTags: MutableMap<String, Tag> =

View File

@@ -0,0 +1,35 @@
package com.willfp.eco.internal.spigot.proxy.v1_18_R1
import com.willfp.eco.core.command.impl.PluginCommand
import com.willfp.eco.internal.spigot.proxy.BukkitCommandsProxy
import org.bukkit.Bukkit
import org.bukkit.command.Command
import org.bukkit.command.SimpleCommandMap
import org.bukkit.craftbukkit.v1_18_R1.CraftServer
import java.lang.reflect.Field
class BukkitCommands : BukkitCommandsProxy {
private val knownCommandsField: Field by lazy {
SimpleCommandMap::class.java.getDeclaredField("knownCommands")
.apply {
isAccessible = true
}
}
@Suppress("UNCHECKED_CAST")
private val knownCommands: MutableMap<String, Command>
get() = knownCommandsField.get(getCommandMap()) as MutableMap<String, Command>
override fun getCommandMap(): SimpleCommandMap {
return (Bukkit.getServer() as CraftServer).commandMap
}
override fun syncCommands() {
(Bukkit.getServer() as CraftServer).syncCommands()
}
override fun unregisterCommand(command: PluginCommand) {
knownCommands.remove(command.name)
knownCommands.remove("${command.plugin.name.lowercase()}:${command.name}")
}
}

View File

@@ -2,11 +2,15 @@ package com.willfp.eco.internal.spigot.proxy.v1_18_R1
import com.willfp.eco.internal.spigot.proxy.CommonsInitializerProxy
import com.willfp.eco.internal.spigot.proxy.common.CommonsProvider
import com.willfp.eco.internal.spigot.proxy.common.toResourceLocation
import net.minecraft.core.Registry
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.Tag
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.item.Item
import org.bukkit.Bukkit
import org.bukkit.Material
import org.bukkit.NamespacedKey
import org.bukkit.craftbukkit.v1_18_R1.CraftServer
import org.bukkit.craftbukkit.v1_18_R1.entity.CraftEntity
@@ -131,5 +135,13 @@ class CommonsInitializer : CommonsInitializerProxy {
}
}
}
override fun materialToItem(material: Material): Item =
Registry.ITEM.getOptional(material.key.toResourceLocation())
.orElseThrow { IllegalArgumentException("Material is not item!") }
override fun itemToMaterial(item: Item) =
Material.getMaterial(Registry.ITEM.getKey(item).path.uppercase())
?: throw IllegalArgumentException("Invalid material!")
}
}

View File

@@ -12,7 +12,6 @@ import org.bukkit.persistence.PersistentDataContainer
import org.bukkit.persistence.PersistentDataType
class ExtendedPersistentDataContainerFactory : ExtendedPersistentDataContainerFactoryProxy {
@Suppress("UNCHECKED_CAST")
private val registry: CraftPersistentDataTypeRegistry
init {
@@ -38,7 +37,7 @@ class ExtendedPersistentDataContainerFactory : ExtendedPersistentDataContainerFa
}
inner class EcoPersistentDataContainer(
val handle: CraftPersistentDataContainer
private val handle: CraftPersistentDataContainer
) : ExtendedPersistentDataContainer {
@Suppress("UNCHECKED_CAST")
private val customDataTags: MutableMap<String, Tag> =

View File

@@ -0,0 +1,35 @@
package com.willfp.eco.internal.spigot.proxy.v1_18_R2
import com.willfp.eco.core.command.impl.PluginCommand
import com.willfp.eco.internal.spigot.proxy.BukkitCommandsProxy
import org.bukkit.Bukkit
import org.bukkit.command.Command
import org.bukkit.command.SimpleCommandMap
import org.bukkit.craftbukkit.v1_18_R2.CraftServer
import java.lang.reflect.Field
class BukkitCommands : BukkitCommandsProxy {
private val knownCommandsField: Field by lazy {
SimpleCommandMap::class.java.getDeclaredField("knownCommands")
.apply {
isAccessible = true
}
}
@Suppress("UNCHECKED_CAST")
private val knownCommands: MutableMap<String, Command>
get() = knownCommandsField.get(getCommandMap()) as MutableMap<String, Command>
override fun getCommandMap(): SimpleCommandMap {
return (Bukkit.getServer() as CraftServer).commandMap
}
override fun syncCommands() {
(Bukkit.getServer() as CraftServer).syncCommands()
}
override fun unregisterCommand(command: PluginCommand) {
knownCommands.remove(command.name)
knownCommands.remove("${command.plugin.name.lowercase()}:${command.name}")
}
}

View File

@@ -2,11 +2,15 @@ package com.willfp.eco.internal.spigot.proxy.v1_18_R2
import com.willfp.eco.internal.spigot.proxy.CommonsInitializerProxy
import com.willfp.eco.internal.spigot.proxy.common.CommonsProvider
import com.willfp.eco.internal.spigot.proxy.common.toResourceLocation
import net.minecraft.core.Registry
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.Tag
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.item.Item
import org.bukkit.Bukkit
import org.bukkit.Material
import org.bukkit.NamespacedKey
import org.bukkit.craftbukkit.v1_18_R2.CraftServer
import org.bukkit.craftbukkit.v1_18_R2.entity.CraftEntity
@@ -131,5 +135,13 @@ class CommonsInitializer : CommonsInitializerProxy {
}
}
}
override fun materialToItem(material: Material): Item =
Registry.ITEM.getOptional(material.key.toResourceLocation())
.orElseThrow { IllegalArgumentException("Material is not item!") }
override fun itemToMaterial(item: Item) =
Material.getMaterial(Registry.ITEM.getKey(item).path.uppercase())
?: throw IllegalArgumentException("Invalid material!")
}
}

View File

@@ -12,7 +12,6 @@ import org.bukkit.persistence.PersistentDataContainer
import org.bukkit.persistence.PersistentDataType
class ExtendedPersistentDataContainerFactory : ExtendedPersistentDataContainerFactoryProxy {
@Suppress("UNCHECKED_CAST")
private val registry: CraftPersistentDataTypeRegistry
init {
@@ -38,7 +37,7 @@ class ExtendedPersistentDataContainerFactory : ExtendedPersistentDataContainerFa
}
inner class EcoPersistentDataContainer(
val handle: CraftPersistentDataContainer
private val handle: CraftPersistentDataContainer
) : ExtendedPersistentDataContainer {
@Suppress("UNCHECKED_CAST")
private val customDataTags: MutableMap<String, Tag> =

View File

@@ -0,0 +1,35 @@
package com.willfp.eco.internal.spigot.proxy.v1_19_R1
import com.willfp.eco.core.command.impl.PluginCommand
import com.willfp.eco.internal.spigot.proxy.BukkitCommandsProxy
import org.bukkit.Bukkit
import org.bukkit.command.Command
import org.bukkit.command.SimpleCommandMap
import org.bukkit.craftbukkit.v1_19_R1.CraftServer
import java.lang.reflect.Field
class BukkitCommands : BukkitCommandsProxy {
private val knownCommandsField: Field by lazy {
SimpleCommandMap::class.java.getDeclaredField("knownCommands")
.apply {
isAccessible = true
}
}
@Suppress("UNCHECKED_CAST")
private val knownCommands: MutableMap<String, Command>
get() = knownCommandsField.get(getCommandMap()) as MutableMap<String, Command>
override fun getCommandMap(): SimpleCommandMap {
return (Bukkit.getServer() as CraftServer).commandMap
}
override fun syncCommands() {
(Bukkit.getServer() as CraftServer).syncCommands()
}
override fun unregisterCommand(command: PluginCommand) {
knownCommands.remove(command.name)
knownCommands.remove("${command.plugin.name.lowercase()}:${command.name}")
}
}

View File

@@ -2,11 +2,15 @@ package com.willfp.eco.internal.spigot.proxy.v1_19_R1
import com.willfp.eco.internal.spigot.proxy.CommonsInitializerProxy
import com.willfp.eco.internal.spigot.proxy.common.CommonsProvider
import com.willfp.eco.internal.spigot.proxy.common.toResourceLocation
import net.minecraft.core.Registry
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.Tag
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.item.Item
import org.bukkit.Bukkit
import org.bukkit.Material
import org.bukkit.NamespacedKey
import org.bukkit.craftbukkit.v1_19_R1.CraftServer
import org.bukkit.craftbukkit.v1_19_R1.entity.CraftEntity
@@ -131,5 +135,13 @@ class CommonsInitializer : CommonsInitializerProxy {
}
}
}
override fun materialToItem(material: Material): Item =
Registry.ITEM.getOptional(material.key.toResourceLocation())
.orElseThrow { IllegalArgumentException("Material is not item!") }
override fun itemToMaterial(item: Item) =
Material.getMaterial(Registry.ITEM.getKey(item).path.uppercase())
?: throw IllegalArgumentException("Invalid material!")
}
}

View File

@@ -12,7 +12,6 @@ import org.bukkit.persistence.PersistentDataContainer
import org.bukkit.persistence.PersistentDataType
class ExtendedPersistentDataContainerFactory : ExtendedPersistentDataContainerFactoryProxy {
@Suppress("UNCHECKED_CAST")
private val registry: CraftPersistentDataTypeRegistry
init {
@@ -38,7 +37,7 @@ class ExtendedPersistentDataContainerFactory : ExtendedPersistentDataContainerFa
}
inner class EcoPersistentDataContainer(
val handle: CraftPersistentDataContainer
private val handle: CraftPersistentDataContainer
) : ExtendedPersistentDataContainer {
@Suppress("UNCHECKED_CAST")
private val customDataTags: MutableMap<String, Tag> =

View File

@@ -0,0 +1,39 @@
plugins {
id("io.papermc.paperweight.userdev") version "1.3.6"
}
group = "com.willfp"
version = rootProject.version
dependencies {
implementation(project(":eco-core:core-nms:nms-common"))
paperDevBundle("1.19.3-R0.1-SNAPSHOT")
implementation("net.kyori:adventure-text-minimessage:4.11.0") {
version {
strictly("4.11.0")
}
exclude(group = "net.kyori", module = "adventure-api")
}
}
tasks {
build {
dependsOn(reobfJar)
}
reobfJar {
mustRunAfter(shadowJar)
}
shadowJar {
relocate(
"com.willfp.eco.internal.spigot.proxy.common",
"com.willfp.eco.internal.spigot.proxy.v1_19_R2.common"
)
relocate(
"net.kyori.adventure.text.minimessage",
"com.willfp.eco.internal.spigot.proxy.v1_19_R2.minimessage"
)
}
}

View File

@@ -0,0 +1,15 @@
package com.willfp.eco.internal.spigot.proxy.v1_19_R2
import com.willfp.eco.internal.spigot.proxy.AutoCraftProxy
import net.minecraft.network.protocol.game.ClientboundPlaceGhostRecipePacket
import net.minecraft.resources.ResourceLocation
class AutoCraft : AutoCraftProxy {
override fun modifyPacket(packet: Any) {
val recipePacket = packet as ClientboundPlaceGhostRecipePacket
val fKey = recipePacket.javaClass.getDeclaredField("b")
fKey.isAccessible = true
val key = fKey[recipePacket] as ResourceLocation
fKey[recipePacket] = ResourceLocation(key.namespace, key.path + "_displayed")
}
}

View File

@@ -0,0 +1,35 @@
package com.willfp.eco.internal.spigot.proxy.v1_19_R2
import com.willfp.eco.core.command.impl.PluginCommand
import com.willfp.eco.internal.spigot.proxy.BukkitCommandsProxy
import org.bukkit.Bukkit
import org.bukkit.command.Command
import org.bukkit.command.SimpleCommandMap
import org.bukkit.craftbukkit.v1_19_R2.CraftServer
import java.lang.reflect.Field
class BukkitCommands : BukkitCommandsProxy {
private val knownCommandsField: Field by lazy {
SimpleCommandMap::class.java.getDeclaredField("knownCommands")
.apply {
isAccessible = true
}
}
@Suppress("UNCHECKED_CAST")
private val knownCommands: MutableMap<String, Command>
get() = knownCommandsField.get(getCommandMap()) as MutableMap<String, Command>
override fun getCommandMap(): SimpleCommandMap {
return (Bukkit.getServer() as CraftServer).commandMap
}
override fun syncCommands() {
(Bukkit.getServer() as CraftServer).syncCommands()
}
override fun unregisterCommand(command: PluginCommand) {
knownCommands.remove(command.name)
knownCommands.remove("${command.plugin.name.lowercase()}:${command.name}")
}
}

View File

@@ -0,0 +1,93 @@
package com.willfp.eco.internal.spigot.proxy.v1_19_R2
import com.willfp.eco.core.display.Display
import com.willfp.eco.internal.spigot.proxy.ChatComponentProxy
import net.kyori.adventure.nbt.api.BinaryTagHolder
import net.kyori.adventure.text.BuildableComponent
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.TranslatableComponent
import net.kyori.adventure.text.event.HoverEvent
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer
import net.minecraft.nbt.TagParser
import org.bukkit.Material
import org.bukkit.craftbukkit.v1_19_R2.inventory.CraftItemStack
import org.bukkit.entity.Player
import org.bukkit.inventory.ItemStack
@Suppress("UNCHECKED_CAST")
class ChatComponent : ChatComponentProxy {
private val gsonComponentSerializer = GsonComponentSerializer.gson()
override fun modifyComponent(obj: Any, player: Player): Any {
if (obj !is net.minecraft.network.chat.Component) {
return obj
}
val component = gsonComponentSerializer.deserialize(
net.minecraft.network.chat.Component.Serializer.toJson(
obj
)
).asComponent() as BuildableComponent<*, *>
val newComponent = modifyBaseComponent(component, player)
return net.minecraft.network.chat.Component.Serializer.fromJson(
gsonComponentSerializer.serialize(newComponent.asComponent())
) ?: obj
}
private fun modifyBaseComponent(baseComponent: Component, player: Player): Component {
var component = baseComponent
if (component is TranslatableComponent) {
val args = mutableListOf<Component>()
for (arg in component.args()) {
args.add(modifyBaseComponent(arg, player))
}
component = component.args(args)
}
val children = mutableListOf<Component>()
for (child in component.children()) {
children.add(modifyBaseComponent(child, player))
}
component = component.children(children)
val hoverEvent: HoverEvent<Any> = component.style().hoverEvent() as HoverEvent<Any>? ?: return component
val showItem = hoverEvent.value()
if (showItem !is HoverEvent.ShowItem) {
return component
}
val newShowItem = showItem.nbt(
BinaryTagHolder.binaryTagHolder(
CraftItemStack.asNMSCopy(
Display.display(
CraftItemStack.asBukkitCopy(
CraftItemStack.asNMSCopy(
ItemStack(
Material.matchMaterial(
showItem.item()
.toString()
) ?: return component,
showItem.count()
)
).apply {
this.tag = TagParser.parseTag(
showItem.nbt()?.string() ?: return component
) ?: return component
}
),
player
)
).orCreateTag.toString()
)
)
val newHover = hoverEvent.value(newShowItem)
val style = component.style().hoverEvent(newHover)
return component.style(style)
}
}

View File

@@ -0,0 +1,147 @@
package com.willfp.eco.internal.spigot.proxy.v1_19_R2
import com.willfp.eco.internal.spigot.proxy.CommonsInitializerProxy
import com.willfp.eco.internal.spigot.proxy.common.CommonsProvider
import com.willfp.eco.internal.spigot.proxy.common.toResourceLocation
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.Tag
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.item.Item
import org.bukkit.Bukkit
import org.bukkit.Material
import org.bukkit.NamespacedKey
import org.bukkit.craftbukkit.v1_19_R2.CraftServer
import org.bukkit.craftbukkit.v1_19_R2.entity.CraftEntity
import org.bukkit.craftbukkit.v1_19_R2.entity.CraftMob
import org.bukkit.craftbukkit.v1_19_R2.inventory.CraftItemStack
import org.bukkit.craftbukkit.v1_19_R2.persistence.CraftPersistentDataContainer
import org.bukkit.craftbukkit.v1_19_R2.persistence.CraftPersistentDataTypeRegistry
import org.bukkit.craftbukkit.v1_19_R2.util.CraftMagicNumbers
import org.bukkit.craftbukkit.v1_19_R2.util.CraftNamespacedKey
import org.bukkit.entity.LivingEntity
import org.bukkit.entity.Mob
import org.bukkit.inventory.ItemStack
import org.bukkit.persistence.PersistentDataContainer
import java.lang.reflect.Field
class CommonsInitializer : CommonsInitializerProxy {
override fun init() {
CommonsProvider.setIfNeeded(CommonsProviderImpl)
}
object CommonsProviderImpl : CommonsProvider {
private val cisHandle: Field = CraftItemStack::class.java.getDeclaredField("handle").apply {
isAccessible = true
}
private val pdcRegsitry = Class.forName("org.bukkit.craftbukkit.v1_19_R2.inventory.CraftMetaItem")
.getDeclaredField("DATA_TYPE_REGISTRY")
.apply { isAccessible = true }
.get(null) as CraftPersistentDataTypeRegistry
override val nbtTagString = CraftMagicNumbers.NBT.TAG_STRING
override fun toPathfinderMob(mob: Mob): PathfinderMob? {
val craft = mob as? CraftMob ?: return null
return craft.handle as? PathfinderMob
}
override fun toResourceLocation(namespacedKey: NamespacedKey): ResourceLocation =
CraftNamespacedKey.toMinecraft(namespacedKey)
override fun asNMSStack(itemStack: ItemStack): net.minecraft.world.item.ItemStack {
return if (itemStack !is CraftItemStack) {
CraftItemStack.asNMSCopy(itemStack)
} else {
cisHandle[itemStack] as net.minecraft.world.item.ItemStack? ?: CraftItemStack.asNMSCopy(itemStack)
}
}
override fun asBukkitStack(itemStack: net.minecraft.world.item.ItemStack): ItemStack {
return CraftItemStack.asBukkitCopy(itemStack)
}
override fun mergeIfNeeded(itemStack: ItemStack, nmsStack: net.minecraft.world.item.ItemStack) {
if (itemStack !is CraftItemStack) {
itemStack.itemMeta = CraftItemStack.asCraftMirror(nmsStack).itemMeta
}
}
override fun toBukkitEntity(entity: net.minecraft.world.entity.LivingEntity): LivingEntity? =
CraftEntity.getEntity(Bukkit.getServer() as CraftServer, entity) as? LivingEntity
override fun makePdc(tag: CompoundTag, base: Boolean): PersistentDataContainer {
fun emptyPdc(): CraftPersistentDataContainer = CraftPersistentDataContainer(pdcRegsitry)
fun CompoundTag?.toPdc(): PersistentDataContainer {
val pdc = emptyPdc()
this ?: return pdc
val keys = this.allKeys
for (key in keys) {
pdc.put(key, this[key])
}
return pdc
}
return if (base) {
tag.toPdc()
} else {
if (tag.contains("PublicBukkitValues")) {
tag.getCompound("PublicBukkitValues").toPdc()
} else {
emptyPdc()
}
}
}
override fun setPdc(
tag: CompoundTag,
pdc: PersistentDataContainer?,
item: net.minecraft.world.item.ItemStack?
) {
fun CraftPersistentDataContainer.toTag(): CompoundTag {
val compound = CompoundTag()
val rawPublicMap: Map<String, Tag> = this.raw
for ((key, value) in rawPublicMap) {
compound.put(key, value)
}
return compound
}
val container = when (pdc) {
is CraftPersistentDataContainer? -> pdc
else -> null
}
if (item != null) {
if (container != null && !container.isEmpty) {
for (key in tag.allKeys.toSet()) {
tag.remove(key)
}
tag.merge(container.toTag())
} else {
item.tag = null
}
} else {
if (container != null && !container.isEmpty) {
tag.put("PublicBukkitValues", container.toTag())
} else {
tag.remove("PublicBukkitValues")
}
}
}
override fun materialToItem(material: Material): Item =
BuiltInRegistries.ITEM.getOptional(material.key.toResourceLocation())
.orElseThrow { IllegalArgumentException("Material is not item!") }
override fun itemToMaterial(item: Item) =
Material.getMaterial(BuiltInRegistries.ITEM.getKey(item).path.uppercase())
?: throw IllegalArgumentException("Invalid material!")
}
}

View File

@@ -0,0 +1,16 @@
package com.willfp.eco.internal.spigot.proxy.v1_19_R2
import com.willfp.eco.internal.entities.EcoDummyEntity
import com.willfp.eco.internal.spigot.proxy.DummyEntityFactoryProxy
import org.bukkit.Location
import org.bukkit.craftbukkit.v1_19_R2.CraftWorld
import org.bukkit.entity.Entity
import org.bukkit.entity.EntityType
class DummyEntityFactory : DummyEntityFactoryProxy {
override fun createDummyEntity(location: Location): Entity {
val world = location.world as CraftWorld
@Suppress("UsePropertyAccessSyntax")
return EcoDummyEntity(world.createEntity(location, EntityType.ZOMBIE.entityClass).getBukkitEntity())
}
}

View File

@@ -0,0 +1,12 @@
package com.willfp.eco.internal.spigot.proxy.v1_19_R2
import com.willfp.eco.core.entities.ai.EntityController
import com.willfp.eco.internal.spigot.proxy.EntityControllerFactoryProxy
import com.willfp.eco.internal.spigot.proxy.common.ai.EcoEntityController
import org.bukkit.entity.Mob
class EntityControllerFactory : EntityControllerFactoryProxy {
override fun <T : Mob> createEntityController(entity: T): EntityController<T> {
return EcoEntityController(entity)
}
}

View File

@@ -0,0 +1,82 @@
package com.willfp.eco.internal.spigot.proxy.v1_19_R2
import com.willfp.eco.core.data.ExtendedPersistentDataContainer
import com.willfp.eco.internal.spigot.proxy.ExtendedPersistentDataContainerFactoryProxy
import net.minecraft.nbt.Tag
import org.bukkit.Material
import org.bukkit.craftbukkit.v1_19_R2.inventory.CraftItemStack
import org.bukkit.craftbukkit.v1_19_R2.persistence.CraftPersistentDataContainer
import org.bukkit.craftbukkit.v1_19_R2.persistence.CraftPersistentDataTypeRegistry
import org.bukkit.inventory.ItemStack
import org.bukkit.persistence.PersistentDataContainer
import org.bukkit.persistence.PersistentDataType
class ExtendedPersistentDataContainerFactory : ExtendedPersistentDataContainerFactoryProxy {
private val registry: CraftPersistentDataTypeRegistry
init {
/*
Can't grab actual instance since it's in CraftMetaItem (which is package-private)
And getting it would mean more janky reflection
*/
val item = CraftItemStack.asCraftCopy(ItemStack(Material.STONE))
val pdc = item.itemMeta!!.persistentDataContainer
this.registry = CraftPersistentDataContainer::class.java.getDeclaredField("registry")
.apply { isAccessible = true }.get(pdc) as CraftPersistentDataTypeRegistry
}
override fun adapt(pdc: PersistentDataContainer): ExtendedPersistentDataContainer {
return when (pdc) {
is CraftPersistentDataContainer -> EcoPersistentDataContainer(pdc)
else -> throw IllegalArgumentException("Custom PDC instance ims not supported!")
}
}
override fun newPdc(): PersistentDataContainer {
return CraftPersistentDataContainer(registry)
}
inner class EcoPersistentDataContainer(
private val handle: CraftPersistentDataContainer
) : ExtendedPersistentDataContainer {
@Suppress("UNCHECKED_CAST")
private val customDataTags: MutableMap<String, Tag> =
CraftPersistentDataContainer::class.java.getDeclaredField("customDataTags")
.apply { isAccessible = true }.get(handle) as MutableMap<String, Tag>
override fun <T : Any, Z : Any> set(key: String, dataType: PersistentDataType<T, Z>, value: Z) {
customDataTags[key] =
registry.wrap(dataType.primitiveType, dataType.toPrimitive(value, handle.adapterContext))
}
override fun <T : Any, Z : Any> has(key: String, dataType: PersistentDataType<T, Z>): Boolean {
val value = customDataTags[key] ?: return false
return registry.isInstanceOf(dataType.primitiveType, value)
}
override fun <T : Any, Z : Any> get(key: String, dataType: PersistentDataType<T, Z>): Z? {
val value = customDataTags[key] ?: return null
return dataType.fromPrimitive(registry.extract(dataType.primitiveType, value), handle.adapterContext)
}
override fun <T : Any, Z : Any> getOrDefault(
key: String,
dataType: PersistentDataType<T, Z>,
defaultValue: Z
): Z {
return get(key, dataType) ?: defaultValue
}
override fun remove(key: String) {
customDataTags.remove(key)
}
override fun getAllKeys(): MutableSet<String> {
return customDataTags.keys
}
override fun getBase(): PersistentDataContainer {
return handle
}
}
}

View File

@@ -0,0 +1,12 @@
package com.willfp.eco.internal.spigot.proxy.v1_19_R2
import com.willfp.eco.core.fast.FastItemStack
import com.willfp.eco.internal.spigot.proxy.FastItemStackFactoryProxy
import com.willfp.eco.internal.spigot.proxy.common.item.EcoFastItemStack
import org.bukkit.inventory.ItemStack
class FastItemStackFactory : FastItemStackFactoryProxy {
override fun create(itemStack: ItemStack): FastItemStack {
return EcoFastItemStack(itemStack)
}
}

View File

@@ -0,0 +1,33 @@
package com.willfp.eco.internal.spigot.proxy.v1_19_R2
import com.willfp.eco.core.display.Display
import com.willfp.eco.internal.spigot.proxy.MiniMessageTranslatorProxy
import com.willfp.eco.util.toLegacy
import net.kyori.adventure.text.minimessage.MiniMessage
class MiniMessageTranslator : MiniMessageTranslatorProxy {
override fun format(message: String): String {
var mut = message
val startsWithPrefix = mut.startsWith(Display.PREFIX)
if (startsWithPrefix) {
mut = mut.substring(2)
}
mut = mut.replace('§', '&')
val miniMessage = runCatching {
MiniMessage.miniMessage().deserialize(
mut
).toLegacy()
}.getOrNull() ?: mut
mut = if (startsWithPrefix) {
Display.PREFIX + miniMessage
} else {
miniMessage
}
return mut
}
}

View File

@@ -0,0 +1,52 @@
package com.willfp.eco.internal.spigot.proxy.v1_19_R2
import com.willfp.eco.core.items.TestableItem
import com.willfp.eco.core.recipe.parts.EmptyTestableItem
import com.willfp.eco.internal.spigot.proxy.SNBTConverterProxy
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.SnbtPrinterTagVisitor
import net.minecraft.nbt.TagParser
import org.bukkit.craftbukkit.v1_19_R2.inventory.CraftItemStack
import org.bukkit.inventory.ItemStack
class SNBTConverter : SNBTConverterProxy {
override fun fromSNBT(snbt: String): ItemStack? {
val nbt = runCatching { TagParser.parseTag(snbt) }.getOrNull() ?: return null
val nms = net.minecraft.world.item.ItemStack.of(nbt)
return CraftItemStack.asBukkitCopy(nms)
}
override fun toSNBT(itemStack: ItemStack): String {
val nms = CraftItemStack.asNMSCopy(itemStack)
return SnbtPrinterTagVisitor().visit(nms.save(CompoundTag()))
}
override fun makeSNBTTestable(snbt: String): TestableItem {
val nbt = runCatching { TagParser.parseTag(snbt) }.getOrNull() ?: return EmptyTestableItem()
val nms = net.minecraft.world.item.ItemStack.of(nbt)
if (nms == net.minecraft.world.item.ItemStack.EMPTY) {
return EmptyTestableItem()
}
nbt.remove("Count")
return SNBTTestableItem(CraftItemStack.asBukkitCopy(nms), nbt)
}
class SNBTTestableItem(
private val item: ItemStack,
private val tag: CompoundTag
) : TestableItem {
override fun matches(itemStack: ItemStack?): Boolean {
if (itemStack == null) {
return false
}
val nms = CraftItemStack.asNMSCopy(itemStack)
val nmsTag = nms.save(CompoundTag())
nmsTag.remove("Count")
return tag.copy().merge(nmsTag) == nmsTag
}
override fun getItem(): ItemStack = item
}
}

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