diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b7c52f0..12f335f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -38,8 +38,8 @@ jobs: with: gradle-home-cache-cleanup: true - - name: Build + tests - run: ./gradlew --no-daemon build shadowJar + - name: Build + tests (all modules) + shadowJar + run: ./gradlew --no-daemon build :plugin:shadowJar - name: Publish test report if: always() @@ -49,14 +49,33 @@ jobs: fail_on_failure: true require_tests: false - - name: Resolve JAR path - id: jar - run: echo "path=$(ls build/libs/AbstractMenus-*.jar | head -n1)" >> "$GITHUB_OUTPUT" + - name: Resolve plugin JAR path + id: plugin_jar + # Skip the dev-only -thin jar; only the shaded fat jar ships. + run: echo "path=$(ls plugin/build/libs/AbstractMenus-*.jar | grep -v -- '-thin\.jar$' | head -n1)" >> "$GITHUB_OUTPUT" - - name: Upload shaded JAR + - name: Resolve API JAR paths + id: api_jars + run: | + echo "binary=$(ls api/build/libs/AbstractMenus-api-*.jar | grep -v 'sources\|javadoc' | head -n1)" >> "$GITHUB_OUTPUT" + echo "sources=$(ls api/build/libs/AbstractMenus-api-*-sources.jar | head -n1)" >> "$GITHUB_OUTPUT" + echo "javadoc=$(ls api/build/libs/AbstractMenus-api-*-javadoc.jar | head -n1)" >> "$GITHUB_OUTPUT" + + - name: Upload plugin shadow JAR uses: actions/upload-artifact@v4 with: name: AbstractMenus-jar - path: ${{ steps.jar.outputs.path }} + path: ${{ steps.plugin_jar.outputs.path }} + if-no-files-found: error + retention-days: 14 + + - name: Upload api artifacts + uses: actions/upload-artifact@v4 + with: + name: AbstractMenus-api + path: | + ${{ steps.api_jars.outputs.binary }} + ${{ steps.api_jars.outputs.sources }} + ${{ steps.api_jars.outputs.javadoc }} if-no-files-found: error retention-days: 14 diff --git a/.github/workflows/publish-api.yml b/.github/workflows/publish-api.yml new file mode 100644 index 0000000..e3113f2 --- /dev/null +++ b/.github/workflows/publish-api.yml @@ -0,0 +1,40 @@ +name: Publish API to GitHub Packages + +on: + release: + types: [published] + workflow_dispatch: + inputs: + tag: + description: 'Release tag to re-publish' + required: true + type: string + +permissions: + contents: read + packages: write + +jobs: + publish: + name: Publish :api to GH Packages + runs-on: ubuntu-latest + steps: + - name: Checkout (release tag) + uses: actions/checkout@v4 + with: + ref: ${{ github.event.release.tag_name || inputs.tag }} + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Publish :api + run: ./gradlew --no-daemon :api:publish + env: + GITHUB_ACTOR: ${{ github.actor }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0a09a3b..b77bde6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,7 +6,7 @@ on: workflow_dispatch: inputs: tag: - description: 'Existing release tag to attach a rebuilt JAR to' + description: 'Existing release tag to attach rebuilt JARs to' required: true type: string @@ -15,7 +15,7 @@ permissions: jobs: attach-jar: - name: Build + attach JAR to release + name: Build + attach JARs to release runs-on: ubuntu-latest steps: - name: Checkout (release tag) @@ -36,30 +36,39 @@ jobs: - name: Verify tag matches project version run: | TAG="${{ github.event.release.tag_name || inputs.tag }}" - # Strip a leading 'v' if present (v1.18.0 → 1.18.0) TAG_VERSION="${TAG#v}" - GRADLE_VERSION=$(grep -E "^version\s+['\"]" build.gradle | sed -E "s/.*['\"]([^'\"]+)['\"].*/\1/") - echo "release tag: $TAG" - echo "tag version: $TAG_VERSION" - echo "gradle version: $GRADLE_VERSION" + GRADLE_VERSION=$(grep -E "^\s*version\s*=?\s*['\"]" build.gradle | head -n1 | sed -E "s/.*['\"]([^'\"]+)['\"].*/\1/") + echo "release tag: $TAG" + echo "tag version: $TAG_VERSION" + echo "gradle version: $GRADLE_VERSION" if [ "$TAG_VERSION" != "$GRADLE_VERSION" ]; then - echo "::error::Release tag version ($TAG_VERSION) does not match build.gradle version ($GRADLE_VERSION). Bump build.gradle before tagging, or retag." + echo "::error::Release tag version ($TAG_VERSION) does not match root build.gradle version ($GRADLE_VERSION). Bump build.gradle before tagging, or retag." exit 1 fi - - name: Build shaded JAR (skip tests — already run in Build workflow on the tagged commit) - run: ./gradlew --no-daemon shadowJar + - name: Build shadow plugin + api artifacts (skip tests — already run on the tagged commit by build.yml) + run: ./gradlew --no-daemon :plugin:shadowJar :api:build - - name: Resolve JAR path - id: jar + - name: Resolve artifact paths + id: paths run: | - JAR=$(ls build/libs/AbstractMenus-*.jar | head -n1) - echo "path=$JAR" >> "$GITHUB_OUTPUT" - echo "name=$(basename "$JAR")" >> "$GITHUB_OUTPUT" + # Skip the dev-only -thin jar; only the shaded fat jar ships. + PLUGIN=$(ls plugin/build/libs/AbstractMenus-*.jar | grep -v -- '-thin\.jar$' | head -n1) + API_BIN=$(ls api/build/libs/AbstractMenus-api-*.jar | grep -v 'sources\|javadoc' | head -n1) + API_SRC=$(ls api/build/libs/AbstractMenus-api-*-sources.jar | head -n1) + API_DOC=$(ls api/build/libs/AbstractMenus-api-*-javadoc.jar | head -n1) + echo "plugin=$PLUGIN" >> "$GITHUB_OUTPUT" + echo "api_bin=$API_BIN" >> "$GITHUB_OUTPUT" + echo "api_src=$API_SRC" >> "$GITHUB_OUTPUT" + echo "api_doc=$API_DOC" >> "$GITHUB_OUTPUT" - - name: Attach JAR to release + - name: Attach JARs to release uses: softprops/action-gh-release@v2 with: tag_name: ${{ github.event.release.tag_name || inputs.tag }} - files: ${{ steps.jar.outputs.path }} + files: | + ${{ steps.paths.outputs.plugin }} + ${{ steps.paths.outputs.api_bin }} + ${{ steps.paths.outputs.api_src }} + ${{ steps.paths.outputs.api_doc }} fail_on_unmatched_files: true diff --git a/.gitignore b/.gitignore index 6dea9cd..9bb1ddf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .gradle .idea build -.DS_Strore \ No newline at end of file +.DS_Store +logs \ No newline at end of file diff --git a/api/build.gradle b/api/build.gradle new file mode 100644 index 0000000..43bc7df --- /dev/null +++ b/api/build.gradle @@ -0,0 +1,77 @@ +plugins { + id 'java-library' + id 'maven-publish' +} + +// Local file in api/build/libs/ is AbstractMenus-api-.jar so +// it visually groups with AbstractMenus-.jar from :plugin and an +// op who copies both off CI artifacts can tell at a glance both are AM. +// +// Maven publish artifactId stays 'api' (project.name) so the coord +// 'ru.abstractmenus:api:' is unchanged for consumers - Gradle's +// maven-publish renames files to -.jar inside the +// repository regardless of what archivesName is on the local jar task. +base { + archivesName = 'AbstractMenus-api' +} + +repositories { + // root already supplies papermc + jitpack + mavenCentral + mavenLocal +} + +dependencies { + compileOnly 'io.papermc.paper:paper-api:1.21.11-R0.1-SNAPSHOT' + api 'com.github.AbstractMenus:hocon:1.0.6' + + compileOnly 'org.projectlombok:lombok:1.18.44' + annotationProcessor 'org.projectlombok:lombok:1.18.44' + + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.14.3' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.14.3' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' +} + +java { + withSourcesJar() + withJavadocJar() +} + +javadoc { + title = "AbstractMenus ${project.version} API" + options.addStringOption('Xdoclint:none', '-quiet') +} + +publishing { + publications { + maven(MavenPublication) { + from components.java + // artifactId defaults to project.name == 'api' + // → Maven coord: ru.abstractmenus:api: + + pom { + name = 'AbstractMenus API' + description = 'Public API for the AbstractMenus Minecraft plugin (Paper/Folia).' + url = 'https://github.com/AbstractMenus/minecraft-plugin' + licenses { + license { + name = 'MIT License' + url = 'https://github.com/AbstractMenus/minecraft-plugin/blob/master/LICENSE' + } + } + scm { + url = 'https://github.com/AbstractMenus/minecraft-plugin' + } + } + } + } + repositories { + maven { + name = 'GitHubPackages' + url = uri('https://maven.pkg.github.com/AbstractMenus/minecraft-plugin') + credentials { + username = System.getenv('GITHUB_ACTOR') + password = System.getenv('GITHUB_TOKEN') + } + } + } +} diff --git a/api/src/main/java/ru/abstractmenus/api/AbstractMenusApi.java b/api/src/main/java/ru/abstractmenus/api/AbstractMenusApi.java new file mode 100644 index 0000000..611909c --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/AbstractMenusApi.java @@ -0,0 +1,140 @@ +package ru.abstractmenus.api; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import ru.abstractmenus.api.inventory.ItemProperty; +import ru.abstractmenus.api.inventory.Menu; +import ru.abstractmenus.api.variables.VariableManager; +import ru.abstractmenus.hocon.api.serialize.NodeSerializers; + +import java.util.Optional; + +/** + * Runtime entry point for AbstractMenus. Exposes the type registries, the + * shared HOCON {@link NodeSerializers}, menu lifecycle operations, and the + * variable manager. + * + *

Obtain the running instance via {@link #get()}: + * + *

{@code
+ * AbstractMenusApi api = AbstractMenusApi.get();
+ * api.actions().register("myAction", MyAction.class, new MyAction.Serializer(), this);
+ * }
+ * + *

The implementation is registered against Bukkit's {@code ServicesManager} + * during the plugin's {@code onEnable()}. Do not call {@link #get()} before + * AbstractMenus has enabled — you will get {@code null}. For + * plugin-as-addon (Path 1) usage, call from your own {@code onEnable()} with + * {@code depend: [AbstractMenus]} in {@code plugin.yml} to guarantee ordering. + * + * @see MenuExtension + * @see TypeRegistry + */ +public interface AbstractMenusApi { + + /** + * Look up the running API instance from Bukkit's ServicesManager. + * + * @return the running API, or {@code null} if AbstractMenus has not + * enabled yet + */ + static AbstractMenusApi get() { + return Bukkit.getServicesManager().load(AbstractMenusApi.class); + } + + // ---- Registries ----------------------------------------------------- + + /** @return the action registry */ + TypeRegistry actions(); + + /** @return the rule registry */ + TypeRegistry rules(); + + /** @return the activator registry */ + TypeRegistry activators(); + + /** @return the item-property registry */ + TypeRegistry itemProperties(); + + /** @return the catalog registry */ + TypeRegistry> catalogs(); + + /** + * Handler-provider registry (economy, permissions, levels, placeholders, + * skins). Replaces the old static {@code Handlers} facade. + * + * @return the provider registry + */ + ProviderRegistry providers(); + + /** + * Shared HOCON serializer collection. Type registrations add to this + * automatically; consumers rarely need to access it directly. + * + * @return the shared serializer collection + */ + NodeSerializers serializers(); + + // ---- Subsystems ----------------------------------------------------- + + /** @return the plugin's variable manager */ + VariableManager variables(); + + /** + * The underlying Bukkit {@link Plugin} instance (the AbstractMenus + * plugin itself). Exposed for scheduler access and listener registration; + * prefer the high-level methods on this interface when possible. + * + * @return the plugin + */ + Plugin getPlugin(); + + // ---- Menu lifecycle (migrated from AbstractMenusPlugin) ------------- + + /** + * Reload all menus from {@code plugins/AbstractMenus/menus/}. Called by + * {@code /am reload}. + */ + void loadMenus(); + + /** + * Open a menu for a player with a triggering activator and context. + * + * @param activator the activator that triggered opening (may be + * {@code null} for programmatic opens) + * @param ctx opening context (source entity/block/etc., may be + * {@code null}) + * @param player the viewer + * @param menu the menu to open + */ + void openMenu(Activator activator, Object ctx, Player player, Menu menu); + + /** + * Open a menu for a player with no activator or context. + * + * @param player the viewer + * @param menu the menu to open + */ + void openMenu(Player player, Menu menu); + + /** + * Get the menu currently open for {@code player}. + * + * @param player the viewer + * @return the open menu, or {@link Optional#empty()} if the player has no + * AbstractMenus menu open + */ + Optional

getOpenedMenu(Player player); + + // ---- Meta ----------------------------------------------------------- + + /** + * API version string, taken from the plugin's {@code version}. Purely + * diagnostic — logged on startup and surfaced in + * {@code /am addons list}. + * + * @return the API version (e.g. {@code "2.0.0-alpha"}) + */ + String apiVersion(); +} diff --git a/api/src/main/java/ru/abstractmenus/api/Action.java b/api/src/main/java/ru/abstractmenus/api/Action.java new file mode 100644 index 0000000..c73b55d --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/Action.java @@ -0,0 +1,94 @@ +package ru.abstractmenus.api; + +import org.bukkit.entity.Player; +import ru.abstractmenus.api.inventory.Menu; +import ru.abstractmenus.api.inventory.Item; + +/** + * Executable menu action — the imperative "do something" block attached to + * a menu item, a menu itself, or an {@link Activator}. + * + *

Core ships dozens of built-in actions ({@code openMenu}, {@code takeMoney}, + * {@code giveItem}, {@code runCommand}, {@code sendMessage}, …). Addons + * extend this surface by implementing {@code Action} plus an inner + * {@link ru.abstractmenus.hocon.api.serialize.NodeSerializer NodeSerializer} + * and registering both through {@link AbstractMenusApi#actions()} during + * {@link MenuExtension#onEnable}. + * + *

Example — a webhook action

+ * + *
{@code
+ * public final class SendWebhookAction implements Action {
+ *
+ *     private final String url;
+ *
+ *     public SendWebhookAction(String url) { this.url = url; }
+ *
+ *     @Override
+ *     public void activate(Player player, Menu menu, Item clickedItem) {
+ *         HttpClients.postAsync(url, "{\"user\":\"" + player.getName() + "\"}");
+ *     }
+ *
+ *     public static final class Serializer implements NodeSerializer {
+ *         @Override
+ *         public SendWebhookAction deserialize(Node node, Type type) {
+ *             return new SendWebhookAction(node.get("url").asString());
+ *         }
+ *     }
+ * }
+ *
+ * // Registration inside the addon's onEnable:
+ * api.actions().register("sendWebhook",
+ *         SendWebhookAction.class,
+ *         new SendWebhookAction.Serializer(),
+ *         this);
+ * }
+ * + *

Menu usage

+ * + * Once registered, the action is available to every menu by its HOCON key: + * + *
{@code
+ * actions {
+ *   click: [
+ *     { type: sendWebhook, url: "https://example.com/hook" }
+ *     { type: closeMenu }
+ *   ]
+ * }
+ * }
+ * + *

Threading

+ * + * {@link #activate(Player, Menu, Item)} is invoked on the main server thread + * in response to an inventory event (click, close) or an activator firing. + * Implementations may touch the Bukkit API directly; offload blocking IO to a + * scheduler. + * + * @see Rule + * @see Activator + * @see TypeRegistry + * @see AbstractMenusApi#actions() + */ +@FunctionalInterface +public interface Action { + + /** + * Execute this action in the context of a menu interaction. + * + *

Called by AbstractMenus when the action's enclosing list fires — + * typically a click on a menu item, an activator trigger, or a menu open / + * close hook. + * + * @param player the player who triggered the action; never + * {@code null} and always online + * @param menu the menu in which the action fires; never {@code null} + * @param clickedItem the item that triggered the action, or {@code null} + * when the action was fired by something other than an + * item click (activator, menu-level hook) + * + * @implNote Runs on the main server thread. Do not block on IO — + * schedule long-running work asynchronously. + */ + void activate(Player player, Menu menu, Item clickedItem); + +} diff --git a/api/src/main/java/ru/abstractmenus/api/Activator.java b/api/src/main/java/ru/abstractmenus/api/Activator.java new file mode 100644 index 0000000..5361991 --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/Activator.java @@ -0,0 +1,137 @@ +package ru.abstractmenus.api; + +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; +import ru.abstractmenus.api.inventory.Menu; + +/** + * Event-driven trigger that opens a menu in response to a game event — + * a command, a chat line, an NPC click, a region boundary crossing, a sign + * interaction, and so on. + * + *

Every activator is a Bukkit {@link Listener}: AbstractMenus registers the + * activator with the server's plugin manager automatically when a menu is + * loaded and unregisters it on menu reload / disable. Do not + * register the listener yourself. + * + *

Core ships built-in activators such as {@code command}, {@code chat}, + * {@code clickNPC}, {@code clickEntity}, {@code regionJoin}, and + * {@code signInteract}. Addons extend this surface by subclassing + * {@code Activator}, annotating one or more methods with {@code @EventHandler}, + * and calling {@link #openMenu(Object, Player)} when the trigger fires. + * + *

Example — open a menu on resource-pack load

+ * + *
{@code
+ * public final class PackLoadedActivator extends Activator {
+ *
+ *     @EventHandler
+ *     public void on(PlayerResourcePackStatusEvent e) {
+ *         if (e.getStatus() == Status.SUCCESSFULLY_LOADED) {
+ *             openMenu(e, e.getPlayer());
+ *         }
+ *     }
+ *
+ *     @Override
+ *     public ValueExtractor getValueExtractor() {
+ *         return (obj, key) -> {
+ *             if (!(obj instanceof PlayerResourcePackStatusEvent ev)) return null;
+ *             return switch (key) {
+ *                 case "status" -> ev.getStatus().name();
+ *                 default -> null;
+ *             };
+ *         };
+ *     }
+ *
+ *     public static final class Serializer implements NodeSerializer {
+ *         @Override
+ *         public PackLoadedActivator deserialize(Node node, Type type) {
+ *             return new PackLoadedActivator();
+ *         }
+ *     }
+ * }
+ *
+ * // Registration inside the addon's onEnable:
+ * api.activators().register("packLoaded",
+ *         PackLoadedActivator.class,
+ *         new PackLoadedActivator.Serializer(),
+ *         this);
+ * }
+ * + *

Menu usage

+ * + *
{@code
+ * activators: [
+ *   { type: packLoaded }
+ * ]
+ * }
+ * + *

Threading

+ * + * Event callbacks run on whichever thread Bukkit dispatches the event from — + * typically the main server thread. The {@link #openMenu(Object, Player)} call + * is safe to invoke from there directly. + * + * @see Action + * @see Rule + * @see ValueExtractor + * @see TypeRegistry + * @see AbstractMenusApi#activators() + */ +public abstract class Activator implements Listener { + + /** + * The menu this activator opens. Injected by AbstractMenus via + * {@link #setTargetMenu(Menu)} right after construction and before the + * listener is registered. + */ + protected Menu menu; + + /** + * Bind this activator to the menu it should open. + * + *

Called by AbstractMenus during menu load — addon code should not + * invoke this directly. + * + * @param menu the target menu; never {@code null} + */ + public void setTargetMenu(Menu menu) { + this.menu = menu; + } + + /** + * Open {@link #menu} for {@code player}, carrying {@code ctx} as the + * context object that will be passed to rules, placeholders, and the + * {@link ValueExtractor} returned by {@link #getValueExtractor()}. + * + *

Called from event handlers in subclasses once the trigger condition + * is satisfied. + * + * @param ctx the opening context — typically the Bukkit + * {@code Event} that fired the trigger, but any object the + * activator's {@link ValueExtractor} understands is valid + * @param player the player to open the menu for; never {@code null} and + * expected to be online + * + * @implNote Delegates to + * {@link AbstractMenusApi#openMenu(Activator, Object, Player, Menu)}. + */ + protected void openMenu(Object ctx, Player player) { + AbstractMenusApi.get().openMenu(this, ctx, player, menu); + } + + /** + * Value extractor used to resolve placeholders against the opening + * context object passed to {@link #openMenu(Object, Player)}. + * + *

Override to expose event-specific fields as menu placeholders (for + * instance, the clicked entity's UUID or a region name). + * + * @return the extractor, or {@code null} if this activator does not + * contribute any context placeholders (the default) + */ + public ValueExtractor getValueExtractor() { + return null; + } + +} diff --git a/api/src/main/java/ru/abstractmenus/api/Catalog.java b/api/src/main/java/ru/abstractmenus/api/Catalog.java new file mode 100644 index 0000000..2713681 --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/Catalog.java @@ -0,0 +1,118 @@ +package ru.abstractmenus.api; + +import org.bukkit.entity.Player; +import ru.abstractmenus.api.inventory.Menu; + +import java.util.Collection; + +/** + * Source of data collections rendered across inventory slots by + * {@code generatedMenu} menus. + * + *

A catalog produces a {@link Collection} of arbitrary objects + * ({@code Player}, {@code World}, custom domain entities, …) — + * AbstractMenus paginates the collection across the menu's + * {@code matrix} and renders each object into the layout's item template, + * substituting placeholders through the catalog's {@link ValueExtractor}. + * + *

Core ships built-in catalogs such as {@code players} and {@code worlds}. + * Addons extend this surface by implementing {@code Catalog} plus an inner + * {@link ru.abstractmenus.hocon.api.serialize.NodeSerializer NodeSerializer} + * and registering both through {@link AbstractMenusApi#catalogs()} during + * {@link MenuExtension#onEnable}. + * + *

Example — a clan roster catalog

+ * + *
{@code
+ * public final class ClanMembersCatalog implements Catalog {
+ *
+ *     @Override
+ *     public Collection snapshot(Player player, Menu menu) {
+ *         return ClanService.get().membersOf(player.getUniqueId());
+ *     }
+ *
+ *     @Override
+ *     public ValueExtractor extractor() {
+ *         return (obj, key) -> {
+ *             if (!(obj instanceof ClanMember m)) return null;
+ *             return switch (key) {
+ *                 case "name" -> m.name();
+ *                 case "rank" -> m.rank().name();
+ *                 default    -> null;
+ *             };
+ *         };
+ *     }
+ *
+ *     public static final class Serializer implements NodeSerializer {
+ *         @Override
+ *         public ClanMembersCatalog deserialize(Node node, Type type) {
+ *             return new ClanMembersCatalog();
+ *         }
+ *     }
+ * }
+ *
+ * // Registration inside the addon's onEnable:
+ * api.catalogs().register("clanMembers",
+ *         ClanMembersCatalog.class,
+ *         new ClanMembersCatalog.Serializer(),
+ *         this);
+ * }
+ * + *

Menu usage

+ * + *
{@code
+ * type: generatedMenu
+ * catalog: { type: clanMembers }
+ * matrix: [ "aaaaaaaaa", "aaaaaaaaa", "aaaaaaaaa" ]
+ * items {
+ *   a: {
+ *     material: PLAYER_HEAD
+ *     name: "%name%"
+ *     lore: [ "Rank: %rank%" ]
+ *   }
+ * }
+ * }
+ * + *

Threading

+ * + * {@link #snapshot(Player, Menu)} is called on the main server thread on every + * render / page change. Keep the implementation cheap — return a cached + * snapshot and refresh it asynchronously if the source data is expensive to + * compute. + * + * @param the element type produced by this catalog + * + * @see ValueExtractor + * @see TypeRegistry + * @see AbstractMenusApi#catalogs() + */ +public interface Catalog { + + /** + * Snapshot of the collection to render for {@code player} in {@code menu}. + * + *

Called every time the menu repaints — on open, on page change, + * and on every refresh tick — so implementations should return a + * cheap view over pre-computed state. + * + * @param player the menu viewer; never {@code null} and always online + * @param menu the menu this catalog is attached to; never {@code null} + * @return the collection to paginate across the menu; never {@code null}, + * but may be empty + * + * @implNote Runs on the main server thread. Do not block on IO — + * pre-compute and refresh asynchronously. + */ + Collection snapshot(Player player, Menu menu); + + /** + * Extractor used to resolve placeholders against each element produced by + * {@link #snapshot(Player, Menu)} when rendering the layout's item + * template. + * + * @return the value extractor; may be {@code null} if this catalog's + * elements don't contribute any placeholders + */ + ValueExtractor extractor(); + +} diff --git a/api/src/main/java/ru/abstractmenus/api/Logger.java b/api/src/main/java/ru/abstractmenus/api/Logger.java new file mode 100644 index 0000000..8844746 --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/Logger.java @@ -0,0 +1,86 @@ +package ru.abstractmenus.api; + +/** + * Thin static facade over the plugin's {@link java.util.logging.Logger} for + * user-visible log output. + * + *

Used by both core and addons so log lines end up tagged with the + * AbstractMenus plugin prefix in the server console regardless of which module + * produced them. The underlying logger is injected once, from the plugin's + * {@code onEnable}, via {@link #set(java.util.logging.Logger)}. + * + *

Example

+ * + *
{@code
+ * // Once, inside AbstractMenus#onEnable:
+ * Logger.set(getLogger());
+ *
+ * // Anywhere in core or an addon:
+ * Logger.info("Menu loaded: " + menu.getName());
+ * Logger.warning("Unknown material in " + menu.getName() + ": " + raw);
+ * Logger.severe("Failed to load menu " + menu.getName());
+ * }
+ * + *

Threading

+ * + * The backing {@link java.util.logging.Logger} is thread-safe — calls + * from async tasks are fine. {@link #set(java.util.logging.Logger)} is expected + * to run once on the main server thread before any other method is called. + * + * @see MenuExtension + */ +public final class Logger { + + private static java.util.logging.Logger logger; + + private Logger(){} + + /** + * Install the backing JUL logger. Called once by AbstractMenus core during + * plugin {@code onEnable}. + * + *

Addons should not call this — replacing the logger would + * redirect all subsequent log output (including core's) away from + * the plugin's channel. + * + * @param log the JUL logger to delegate to; never {@code null} + */ + public static void set(java.util.logging.Logger log){ + logger = log; + } + + /** + * Log an {@code INFO}-level message. + * + * @param message the message to log; may contain any characters valid in + * a server console line + */ + public static void info(String message){ + logger.info(message); + } + + /** + * Log a {@code WARNING}-level message. + * + *

Use for recoverable configuration mistakes (unknown material, missing + * placeholder, fallback applied) — not for programmer errors. + * + * @param message the message to log + */ + public static void warning(String message){ + logger.warning(message); + } + + /** + * Log a {@code SEVERE}-level (error) message. + * + *

Use for failures that prevent a menu / action / rule from functioning + * — IO errors, schema violations, unhandled exceptions. + * + * @param message the message to log + */ + public static void severe(String message){ + logger.severe(message); + } + +} diff --git a/api/src/main/java/ru/abstractmenus/api/MenuExtension.java b/api/src/main/java/ru/abstractmenus/api/MenuExtension.java new file mode 100644 index 0000000..eb410b4 --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/MenuExtension.java @@ -0,0 +1,117 @@ +package ru.abstractmenus.api; + +/** + * Contract an AbstractMenus addon implements to register custom types + * (actions, rules, item properties, activators, catalogs) at runtime. + * + *

Two paths for delivery: + * + *

    + *
  • Plugin-as-addon (Path 1) — a standard Bukkit plugin + * that {@code depend}s on AbstractMenus, implements {@code MenuExtension}, + * and looks up the running API via {@link AbstractMenusApi#get()} inside + * its own {@code onEnable()}. Use this when your addon integrates other + * Bukkit plugins (PlayerPoints, WorldGuard, …) and needs Bukkit's + * native plugin dependency ordering.
  • + * + *
  • AM-loaded addon (Path 2) — a lightweight jar with an + * {@code addon.conf} placed in {@code plugins/AbstractMenus/addons/}. + * AbstractMenus loads it through an isolated classloader and calls the + * lifecycle methods below. Use this for pure type extensions with no + * cross-plugin integration. (Shipped in Phase B.2.)
  • + *
+ * + *

Lifecycle

+ * + *
    + *
  1. {@link #onLoad(AbstractMenusApi)} — invoked for every extension + * before any {@code onEnable} runs. Use to perform ordering-independent + * setup. Do not register types here — other extensions + * may not be loaded yet.
  2. + *
  3. {@link #onEnable(AbstractMenusApi)} — invoked in dependency + * order (DAG sort over {@code addonDependencies} from addon.conf). + * Register types / providers here.
  4. + *
  5. {@link #onDisable(AbstractMenusApi)} — invoked on server + * shutdown or {@code /am addons reload}. Release resources (Bukkit + * listeners, scheduler tasks). Types registered through + * {@code api.*()} registries are auto-unregistered by AbstractMenus + * — you don't need to undo those manually.
  6. + *
+ * + *

Thread safety

+ * + * All lifecycle methods are called on the main server thread. Implementations + * are synchronous; offload blocking work to a scheduler. + * + * @see AbstractMenusApi + * @see TypeRegistry + */ +public interface MenuExtension { + + /** + * Pre-enable hook — called for every extension before any + * extension's {@link #onEnable(AbstractMenusApi)} runs. Default is no-op. + * + * @param api the running AbstractMenus API + */ + default void onLoad(AbstractMenusApi api) { + } + + /** + * Register types (and in Phase C, providers) against {@code api}. Called + * in dependency order — extensions listed in this one's + * {@code addonDependencies} are already enabled when this runs. + * + * @param api the running AbstractMenus API + */ + void onEnable(AbstractMenusApi api); + + /** + * Release resources. Called on server shutdown or a reload of this + * extension. Default is no-op. + * + *

Registry entries are cleaned up automatically — you only need + * to unregister Bukkit listeners, cancel scheduler tasks, and close any + * resources you opened yourself. + * + * @param api the running AbstractMenus API + */ + default void onDisable(AbstractMenusApi api) { + } + + /** + * Display name. Used by {@code /am addons list} and log messages. May + * return {@code null}; for AM-loaded addons (Path 2) the name from + * {@code addon.conf} is used as a fallback. + * + * @return the extension display name, or {@code null} to defer + */ + default String name() { + return null; + } + + /** + * Extension version string. Used for diagnostics. May return {@code null} + * — Path 2 falls back to the {@code version} from + * {@code addon.conf}. + * + * @return the version, or {@code null} to defer + */ + default String version() { + return null; + } + + /** + * The AbstractMenus API version this extension was built against. Purely + * diagnostic — logged on load and shown in {@code /am addons info}. + * No compatibility enforcement is performed; if the current API is + * incompatible, the extension will fail naturally on {@code onEnable} + * with a clear stacktrace. + * + * @return the target API version string (e.g. {@code "2.0.0"}), or + * {@code null} if not tracked + */ + default String targetApiVersion() { + return null; + } +} diff --git a/api/src/main/java/ru/abstractmenus/api/ProviderRegistry.java b/api/src/main/java/ru/abstractmenus/api/ProviderRegistry.java new file mode 100644 index 0000000..73d6df8 --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/ProviderRegistry.java @@ -0,0 +1,98 @@ +package ru.abstractmenus.api; + +import ru.abstractmenus.api.handler.EconomyHandler; +import ru.abstractmenus.api.handler.LevelHandler; +import ru.abstractmenus.api.handler.PermissionsHandler; +import ru.abstractmenus.api.handler.PlaceholderHandler; +import ru.abstractmenus.api.handler.SkinHandler; + +import java.util.Collection; + +/** + * Registry of pluggable handler providers (economy, permissions, levels, + * placeholders, skins). Replaces the old static {@code Handlers.set*()/get*()} + * facade with an owner-aware registry that supports multiple providers per + * section plus priority-based auto-resolution. + * + *

Registration

+ * + *
{@code
+ * public final class MyEconomyAddon implements MenuExtension {
+ *     @Override public void onEnable(AbstractMenusApi api) {
+ *         api.providers().registerEconomy(
+ *             "playerpoints",
+ *             new PlayerPointsEconomy(pp),
+ *             100,          // priority — higher wins in auto-resolve
+ *             this);        // owner for unregisterAll on reload
+ *     }
+ * }
+ * }
+ * + *

Resolution

+ * + *
    + *
  • {@link #economy()} — highest-priority registered handler, or + * first-registered on ties. Returns {@code null} if none registered.
  • + *
  • {@link #economy(String)} — explicit lookup by id.
  • + *
  • {@link #allEconomy()} — every registration, for introspection.
  • + *
  • {@link #hasEconomy(String)} — validation helper (used by + * menu-serializers to fail-at-load when a HOCON file references an + * unknown provider).
  • + *
+ * + *

Same shape for permissions / levels / placeholders / skins. + * + * @see AbstractMenusApi#providers() + */ +public interface ProviderRegistry { + + // ---- Economy --------------------------------------------------------- + + void registerEconomy(String id, EconomyHandler handler, int priority, MenuExtension owner); + EconomyHandler economy(); + EconomyHandler economy(String id); + Collection allEconomy(); + boolean hasEconomy(String id); + + // ---- Permissions ----------------------------------------------------- + + void registerPermissions(String id, PermissionsHandler handler, int priority, MenuExtension owner); + PermissionsHandler permissions(); + PermissionsHandler permissions(String id); + Collection allPermissions(); + boolean hasPermissions(String id); + + // ---- Levels ---------------------------------------------------------- + + void registerLevels(String id, LevelHandler handler, int priority, MenuExtension owner); + LevelHandler levels(); + LevelHandler levels(String id); + Collection allLevels(); + boolean hasLevels(String id); + + // ---- Placeholders ---------------------------------------------------- + + void registerPlaceholders(String id, PlaceholderHandler handler, int priority, MenuExtension owner); + PlaceholderHandler placeholders(); + PlaceholderHandler placeholders(String id); + Collection allPlaceholders(); + boolean hasPlaceholders(String id); + + // ---- Skins ----------------------------------------------------------- + + void registerSkins(String id, SkinHandler handler, int priority, MenuExtension owner); + SkinHandler skins(); + SkinHandler skins(String id); + Collection allSkins(); + boolean hasSkins(String id); + + // ---- Cleanup --------------------------------------------------------- + + /** + * Remove every provider registration (across all sections) owned by + * {@code owner}. Called by AddonManager when an addon is disabled. + * + * @param owner the extension whose providers should be cleared + */ + void unregisterAll(MenuExtension owner); +} diff --git a/api/src/main/java/ru/abstractmenus/api/Rule.java b/api/src/main/java/ru/abstractmenus/api/Rule.java new file mode 100644 index 0000000..5ccc1a2 --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/Rule.java @@ -0,0 +1,99 @@ +package ru.abstractmenus.api; + +import org.bukkit.entity.Player; +import ru.abstractmenus.api.inventory.Menu; +import ru.abstractmenus.api.inventory.Item; + +/** + * Boolean predicate attached to a menu, item, or action — decides whether + * the enclosing element is visible, clickable, or allowed to fire. + * + *

Core ships built-in rules such as {@code permission}, {@code hasMoney}, + * {@code level}, {@code world}, {@code region}, and many more. Addons extend + * this surface by implementing {@code Rule} plus an inner + * {@link ru.abstractmenus.hocon.api.serialize.NodeSerializer NodeSerializer} + * and registering both through {@link AbstractMenusApi#rules()} during + * {@link MenuExtension#onEnable}. + * + *

Example — a cooldown rule

+ * + *
{@code
+ * public final class CooldownRule implements Rule {
+ *
+ *     private final String key;
+ *     private final long seconds;
+ *
+ *     public CooldownRule(String key, long seconds) {
+ *         this.key = key;
+ *         this.seconds = seconds;
+ *     }
+ *
+ *     @Override
+ *     public boolean check(Player player, Menu menu, Item clickedItem) {
+ *         return CooldownService.remaining(player.getUniqueId(), key) <= 0;
+ *     }
+ *
+ *     public static final class Serializer implements NodeSerializer {
+ *         @Override
+ *         public CooldownRule deserialize(Node node, Type type) {
+ *             return new CooldownRule(
+ *                 node.get("key").asString(),
+ *                 node.get("seconds").asLong());
+ *         }
+ *     }
+ * }
+ *
+ * // Registration inside the addon's onEnable:
+ * api.rules().register("cooldown",
+ *         CooldownRule.class,
+ *         new CooldownRule.Serializer(),
+ *         this);
+ * }
+ * + *

Menu usage

+ * + *
{@code
+ * rules: [
+ *   { type: cooldown, key: "daily-claim", seconds: 86400 }
+ *   { type: permission, value: "server.daily" }
+ * ]
+ * }
+ * + *

Threading & purity

+ * + * {@link #check(Player, Menu, Item)} runs on the main server thread and must + * be a pure predicate — do not mutate state here. It is called + * frequently (on menu render, tick updates, every item click) so keep it + * cheap; cache anything that hits IO. + * + * @see Action + * @see Activator + * @see TypeRegistry + * @see AbstractMenusApi#rules() + */ +@FunctionalInterface +public interface Rule { + + /** + * Evaluate the predicate for a given player / menu / item context. + * + *

Called whenever AbstractMenus needs to decide whether the element + * wrapping this rule should show, fire, or be clickable. Multiple rules on + * the same element are AND-combined. + * + * @param player the player being evaluated; never {@code null} and + * always online + * @param menu the menu the rule is evaluated against; never + * {@code null} + * @param clickedItem the item involved when the rule is evaluated as part + * of a click, or {@code null} for menu-level / + * item-render evaluation + * @return {@code true} if the player satisfies the rule, {@code false} + * otherwise + * + * @implNote Must be side-effect free and cheap — runs on the main + * thread and is polled frequently. Do not block on IO. + */ + boolean check(Player player, Menu menu, Item clickedItem); + +} diff --git a/api/src/main/java/ru/abstractmenus/api/TypeRegistry.java b/api/src/main/java/ru/abstractmenus/api/TypeRegistry.java new file mode 100644 index 0000000..ecee338 --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/TypeRegistry.java @@ -0,0 +1,103 @@ +package ru.abstractmenus.api; + +import ru.abstractmenus.hocon.api.serialize.NodeSerializer; + +import java.util.Set; + +/** + * Typed registry of menu type classes keyed by a HOCON-visible name. + * + *

AbstractMenus ships five of these, accessible via + * {@link AbstractMenusApi#actions()}, {@link AbstractMenusApi#rules()}, + * {@link AbstractMenusApi#activators()}, + * {@link AbstractMenusApi#itemProperties()}, and + * {@link AbstractMenusApi#catalogs()} — one per extension surface. + * + *

Registration takes a {@link MenuExtension} "owner" so + * {@link #unregisterAll(MenuExtension)} can wipe every entry an addon + * contributed when that addon is disabled or reloaded. + * + *

Example

+ * + *
{@code
+ * public class MyAddon implements MenuExtension {
+ *     @Override
+ *     public void onEnable(AbstractMenusApi api) {
+ *         api.actions().register(
+ *             "sendWebhook",
+ *             SendWebhookAction.class,
+ *             new SendWebhookAction.Serializer(),
+ *             this);
+ *     }
+ * }
+ * }
+ * + *

Threading

+ * + * Implementations are safe for concurrent reads but registration/unregistration + * is expected to happen on the main server thread during plugin / extension + * {@code onEnable} / {@code onDisable} hooks. + * + * @param the registry element base type (e.g. {@code Action}, + * {@code Rule}, {@code ItemProperty}) + * + * @see AbstractMenusApi + * @see MenuExtension + */ +public interface TypeRegistry { + + /** + * Register a subtype under {@code key}. + * + *

Keys are compared case-insensitively (lowercased internally). If the + * key is already registered, this method overwrites the previous entry + * and logs a warning — prefer unique keys with a vendor prefix. + * + *

The {@code serializer} is also registered against the shared + * {@link ru.abstractmenus.hocon.api.serialize.NodeSerializer NodeSerializer} + * collection so HOCON parsing can deserialize instances of this type. + * + * @param the concrete subtype + * @param key HOCON-visible name (case-insensitive) + * @param type the class token; must be assignable to {@code T} + * @param serializer HOCON serializer for {@code type} + * @param owner the registering extension; used for later + * {@link #unregisterAll(MenuExtension)} cleanup. Pass + * the core extension for core registrations. + */ + void register(String key, + Class type, + NodeSerializer serializer, + MenuExtension owner); + + /** + * Look up a registered subtype by key. + * + * @param key HOCON-visible name (case-insensitive) + * @return the registered class, or {@code null} if not found + */ + Class get(String key); + + /** + * Reverse lookup — the HOCON name a given class is registered under. + * + * @param type the class to look up + * @return the registered key, or {@code null} if not registered + */ + String name(Class type); + + /** + * All registered keys. Returned set is an unmodifiable snapshot. + * + * @return all registered keys (lowercased) + */ + Set keys(); + + /** + * Remove every entry registered by {@code owner}. Invoked by the addon + * manager when an extension is disabled or reloaded. + * + * @param owner the extension whose entries should be wiped + */ + void unregisterAll(MenuExtension owner); +} diff --git a/api/src/main/java/ru/abstractmenus/api/ValueExtractor.java b/api/src/main/java/ru/abstractmenus/api/ValueExtractor.java new file mode 100644 index 0000000..45ed61a --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/ValueExtractor.java @@ -0,0 +1,58 @@ +package ru.abstractmenus.api; + +/** + * Strategy for resolving placeholder keys against an arbitrary context object. + * + *

AbstractMenus calls extractors to substitute placeholders like + * {@code %name%}, {@code %location_x%}, or {@code %region_id%} when rendering + * menu items — the context object is whatever the caller provided + * (a Bukkit {@code Event} from an {@link Activator}, a catalog element from + * {@link Catalog#extractor()}, a {@code Block}, {@code Entity}, {@code Region}, + * …). + * + *

Implementations typically start by narrowing the {@link Object} argument + * with an {@code instanceof} check and returning {@code null} for keys the + * extractor doesn't recognize, so the placeholder pipeline can fall through to + * the next extractor in the chain. + * + *

Example

+ * + *
{@code
+ * ValueExtractor locationExtractor = (obj, key) -> {
+ *     if (!(obj instanceof Location loc)) return null;
+ *     return switch (key) {
+ *         case "world" -> loc.getWorld().getName();
+ *         case "x"     -> String.valueOf(loc.getBlockX());
+ *         case "y"     -> String.valueOf(loc.getBlockY());
+ *         case "z"     -> String.valueOf(loc.getBlockZ());
+ *         default      -> null;
+ *     };
+ * };
+ * }
+ * + * @see Activator#getValueExtractor() + * @see Catalog#extractor() + */ +public interface ValueExtractor { + + /** + * Resolve {@code placeholder} against {@code obj} and return the String + * value that should be substituted in the rendered text. + * + *

Return {@code null} when the extractor doesn't recognize the key or + * when {@code obj} is not of a type this extractor handles — + * AbstractMenus will then consult the next extractor in the chain. + * + * @param obj the context object; narrow with {@code instanceof} + * before dereferencing + * @param placeholder the placeholder key (no surrounding {@code %}); e.g. + * {@code "location_x"} or {@code "name"} + * @return the resolved value as a {@link String}, or {@code null} if this + * extractor cannot resolve the key against {@code obj} + * + * @implNote Called on the main server thread during menu rendering — + * keep the body cheap and side-effect free. + */ + String extract(Object obj, String placeholder); + +} diff --git a/api/src/main/java/ru/abstractmenus/api/handler/EconomyHandler.java b/api/src/main/java/ru/abstractmenus/api/handler/EconomyHandler.java new file mode 100644 index 0000000..382dec9 --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/handler/EconomyHandler.java @@ -0,0 +1,137 @@ +package ru.abstractmenus.api.handler; + +import org.bukkit.entity.Player; + +/** + * Handler for economy operations invoked by money-related menu actions and rules. + * + *

An {@code EconomyHandler} adapts a specific economy plugin + * (Vault + EssentialsX, PlayerPoints, TokenManager, …) to the contract + * AbstractMenus calls when a menu declares a money-aware element: + * + *

    + *
  • the {@code takeMoney} / {@code giveMoney} actions call + * {@link #takeBalance(Player, double)} and + * {@link #giveBalance(Player, double)} respectively;
  • + *
  • the {@code hasMoney} rule calls {@link #hasBalance(Player, double)} + * to decide whether the element is shown to the player.
  • + *
+ * + *

Registration

+ * + * A single handler is active at a time. Register yours via + * {@link ru.abstractmenus.api.ProviderRegistry#registerEconomy} inside your + * addon's {@link ru.abstractmenus.api.MenuExtension#onEnable}: + * + *

Example — bridging PlayerPoints

+ * + * Unlike Vault's {@code double}-based currency, PlayerPoints stores an + * {@code int} token count. The handler rounds fractional values towards zero: + * + *
{@code
+ * public final class PlayerPointsEconomy implements EconomyHandler {
+ *
+ *     private final PlayerPointsAPI pp;
+ *
+ *     public PlayerPointsEconomy(PlayerPointsAPI pp) { this.pp = pp; }
+ *
+ *     @Override
+ *     public boolean hasBalance(Player p, double balance) {
+ *         return pp.look(p.getUniqueId()) >= (int) balance;
+ *     }
+ *
+ *     @Override
+ *     public void takeBalance(Player p, double amount) {
+ *         pp.take(p.getUniqueId(), (int) amount);
+ *     }
+ *
+ *     @Override
+ *     public void giveBalance(Player p, double amount) {
+ *         pp.give(p.getUniqueId(), (int) amount);
+ *     }
+ * }
+ * }
+ * + *

Menu usage

+ * + * Once a handler is registered, menus use money primitives without caring + * which backing plugin is wired up: + * + *
{@code
+ * rules: [
+ *   { type: hasMoney, amount: 100 }
+ * ]
+ * actions {
+ *   click: [
+ *     { type: takeMoney, amount: 100 }
+ *     { type: giveItem,  ... }
+ *   ]
+ * }
+ * }
+ * + *

Threading

+ * + * All methods are called on the main server thread in response to inventory + * events. Implementations may touch the Bukkit API directly and should avoid + * blocking IO; if the underlying economy plugin hits a database, cache the + * last known balance and refresh asynchronously. + * + * @see ru.abstractmenus.api.ProviderRegistry#registerEconomy + * @see PermissionsHandler + * @see LevelHandler + */ +public interface EconomyHandler { + + /** + * Returns {@code true} if {@code player} has at least {@code balance} + * in this economy. Backs the {@code hasMoney} menu rule. + * + *

This is a pure predicate — implementations must not mutate + * state. Use {@link #takeBalance(Player, double)} to withdraw after a + * successful check. + * + * @param player the player being checked; never {@code null} and always + * online (called in response to an inventory event) + * @param balance the required amount; non-negative + * @return {@code true} if the player has at least {@code balance}, + * {@code false} otherwise — including when the player is + * unknown to the backing economy plugin + * + * @implNote Callers assume this is cheap — avoid blocking IO. If + * the economy plugin requires IO, serve the last cached value + * and refresh asynchronously. + */ + boolean hasBalance(Player player, double balance); + + /** + * Deducts {@code amount} from {@code player}'s balance. Backs the + * {@code takeMoney} menu action. + * + *

AbstractMenus does not re-check the balance before + * calling this method, even when the menu pairs the action with a + * {@code hasMoney} rule. Implementations that need to reject the + * withdrawal (e.g. the balance went negative between the check and the + * call) should fail silently — there is no way to cancel an + * in-flight action from the handler. + * + * @param player the player to charge; never {@code null}, always online + * @param amount the amount to withdraw; non-negative. Handlers bridging + * an integer currency (PlayerPoints, TokenManager) should + * round towards zero ({@code (int) amount}) to match + * {@link #giveBalance(Player, double)}. + */ + void takeBalance(Player player, double amount); + + /** + * Adds {@code amount} to {@code player}'s balance. Backs the + * {@code giveMoney} menu action. + * + * @param player the player to credit; never {@code null}, always online + * @param amount the amount to deposit; non-negative + * + * @implNote Handlers bridging an integer currency should round + * {@code amount} towards zero, matching + * {@link #takeBalance(Player, double)}. + */ + void giveBalance(Player player, double amount); +} diff --git a/api/src/main/java/ru/abstractmenus/api/handler/LevelHandler.java b/api/src/main/java/ru/abstractmenus/api/handler/LevelHandler.java new file mode 100644 index 0000000..49f6c19 --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/handler/LevelHandler.java @@ -0,0 +1,184 @@ +package ru.abstractmenus.api.handler; + +import org.bukkit.entity.Player; + +/** + * Handler for experience and level operations invoked by XP/level-related menu + * actions and rules. + * + *

A {@code LevelHandler} adapts a specific levelling backend (vanilla + * Minecraft XP bar, MMOCore, Heroes, AureliumSkills, …) to the contract + * AbstractMenus calls when a menu declares a level-aware element: + * + *

    + *
  • the {@code giveXp} / {@code takeXp} actions call + * {@link #giveXp(Player, int)} and {@link #takeXp(Player, int)};
  • + *
  • the {@code giveLevel} / {@code takeLevel} actions call + * {@link #giveLevel(Player, int)} and {@link #takeLevel(Player, int)};
  • + *
  • the {@code xp} rule calls {@link #getXp(Player)} and the + * {@code level} rule calls {@link #getLevel(Player)} to decide whether + * the element is shown to the player.
  • + *
+ * + *

Registration

+ * + * Multiple handlers may coexist — the highest-priority one is picked + * when a menu does not name a provider explicitly. Register yours via + * {@link ru.abstractmenus.api.ProviderRegistry#registerLevels} inside your + * addon's {@link ru.abstractmenus.api.MenuExtension#onEnable}. + * + *

Example — bridging MMOCore

+ * + * MMOCore tracks class-specific experience and levels that live outside the + * vanilla XP bar. A bridge delegates to its {@code PlayerData} API: + * + *
{@code
+ * public final class MMOCoreLevels implements LevelHandler {
+ *
+ *     @Override
+ *     public int getXp(Player p) {
+ *         return (int) PlayerData.get(p).getExperience();
+ *     }
+ *
+ *     @Override
+ *     public void giveXp(Player p, int xp) {
+ *         PlayerData.get(p).giveExperience(xp, EXPSource.SOURCE_MENU);
+ *     }
+ *
+ *     @Override
+ *     public void takeXp(Player p, int xp) {
+ *         PlayerData.get(p).giveExperience(-xp, EXPSource.SOURCE_MENU);
+ *     }
+ *
+ *     @Override
+ *     public int getLevel(Player p) {
+ *         return PlayerData.get(p).getLevel();
+ *     }
+ *
+ *     @Override
+ *     public void giveLevel(Player p, int level) {
+ *         PlayerData.get(p).giveLevels(level, EXPSource.SOURCE_MENU);
+ *     }
+ *
+ *     @Override
+ *     public void takeLevel(Player p, int level) {
+ *         PlayerData.get(p).giveLevels(-level, EXPSource.SOURCE_MENU);
+ *     }
+ * }
+ * }
+ * + *

Menu usage

+ * + * Once a handler is registered, menus use XP/level primitives without caring + * which backing plugin is wired up: + * + *
{@code
+ * rules: [
+ *   { type: level, value: ">=10" }
+ * ]
+ * actions {
+ *   click: [
+ *     { type: takeXp,    value: 50 }
+ *     { type: giveLevel, value: 1  }
+ *   ]
+ * }
+ * }
+ * + *

Threading

+ * + * All methods are called on the main server thread in response to inventory + * events. Implementations may touch the Bukkit API directly and should avoid + * blocking IO; if the underlying levelling plugin hits a database, serve the + * last known value from memory and refresh asynchronously. + * + * @see ru.abstractmenus.api.ProviderRegistry#registerLevels + * @see EconomyHandler + * @see PermissionsHandler + */ +public interface LevelHandler { + + /** + * Returns the player's current experience count. Backs the {@code xp} + * menu rule. + * + *

This is a pure getter — implementations must not mutate state. + * + * @param player the player being inspected; never {@code null} and always + * online (called in response to an inventory event) + * @return the player's current XP count; {@code 0} if the player is + * unknown to the backing levelling plugin + * + * @implNote Callers assume this is cheap — avoid blocking IO. If the + * levelling plugin requires IO, serve the last cached value and + * refresh asynchronously. + */ + int getXp(Player player); + + /** + * Adds {@code xp} experience points to the player. Backs the + * {@code giveXp} menu action. + * + * @param player the player to credit; never {@code null}, always online + * @param xp the XP to grant; non-negative + * + * @implNote If the backend exposes a named XP source (MMOCore's + * {@code EXPSource}, Heroes' {@code ExperienceType}), tag the + * grant as originating from a menu so the backend can treat it + * differently from mob-kill XP when needed. + */ + void giveXp(Player player, int xp); + + /** + * Deducts {@code xp} experience points from the player. Backs the + * {@code takeXp} menu action. + * + *

AbstractMenus does not re-check the XP before + * calling this method, even when the menu pairs the action with an + * {@code xp} rule. Implementations that need to reject the withdrawal + * (e.g. the player's XP went negative between the check and the call) + * should fail silently or clamp to zero — there is no way to cancel + * an in-flight action from the handler. + * + * @param player the player to charge; never {@code null}, always online + * @param xp the XP to withdraw; non-negative + */ + void takeXp(Player player, int xp); + + /** + * Returns the player's current level. Backs the {@code level} menu rule. + * + *

This is a pure getter — implementations must not mutate state. + * + * @param player the player being inspected; never {@code null} and always + * online + * @return the player's current level; {@code 0} if the player is unknown + * to the backing levelling plugin + * + * @implNote Callers assume this is cheap — avoid blocking IO. Cache + * and refresh asynchronously if the backend requires IO. + */ + int getLevel(Player player); + + /** + * Adds {@code level} levels to the player. Backs the {@code giveLevel} + * menu action. + * + * @param player the player to credit; never {@code null}, always online + * @param level the number of levels to grant; non-negative + */ + void giveLevel(Player player, int level); + + /** + * Deducts {@code level} levels from the player. Backs the + * {@code takeLevel} menu action. + * + *

AbstractMenus does not re-check the level before + * calling this method. Implementations that need to reject the withdrawal + * should fail silently or clamp to zero. + * + * @param player the player to charge; never {@code null}, always online + * @param level the number of levels to withdraw; non-negative + */ + void takeLevel(Player player, int level); + +} diff --git a/api/src/main/java/ru/abstractmenus/api/handler/PermissionsHandler.java b/api/src/main/java/ru/abstractmenus/api/handler/PermissionsHandler.java new file mode 100644 index 0000000..ad3f3cb --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/handler/PermissionsHandler.java @@ -0,0 +1,213 @@ +package ru.abstractmenus.api.handler; + +import org.bukkit.entity.Player; + +/** + * Handler for permission and group operations invoked by permission-related + * menu actions and rules. + * + *

A {@code PermissionsHandler} adapts a specific permissions backend + * (LuckPerms, PermissionsEx, GroupManager, …) to the contract + * AbstractMenus calls when a menu declares a permission-aware element: + * + *

    + *
  • the {@code givePermission} / {@code removePermission} actions call + * {@link #addPermission(Player, String)} and + * {@link #removePermission(Player, String)};
  • + *
  • the {@code addGroup} / {@code removeGroup} actions call + * {@link #addGroup(Player, String)} and + * {@link #removeGroup(Player, String)};
  • + *
  • the {@code permission} rule calls + * {@link #hasPermission(Player, String)} and the {@code group} rule + * calls {@link #hasGroup(Player, String)} to decide whether the element + * is shown to the player.
  • + *
+ * + *

Registration

+ * + * Multiple handlers may coexist — the highest-priority one is picked + * when a menu does not name a provider explicitly. Register yours via + * {@link ru.abstractmenus.api.ProviderRegistry#registerPermissions} inside + * your addon's {@link ru.abstractmenus.api.MenuExtension#onEnable}. + * + *

If no permissions handler is registered, permission nodes are checked + * against Bukkit's in-memory permission set (persisted only as long as the + * player session lives) and group operations become no-ops. Register a proper + * LuckPerms-backed handler to persist grants across reloads. + * + *

Example — bridging LuckPerms

+ * + * LuckPerms' {@code User} API exposes node-level mutation. The bridge loads + * the user, mutates, and saves: + * + *
{@code
+ * public final class LuckPermsPermissions implements PermissionsHandler {
+ *
+ *     private final LuckPerms lp;
+ *
+ *     public LuckPermsPermissions(LuckPerms lp) { this.lp = lp; }
+ *
+ *     @Override
+ *     public void addPermission(Player p, String permission) {
+ *         lp.getUserManager().modifyUser(p.getUniqueId(), u ->
+ *             u.data().add(Node.builder(permission).build()));
+ *     }
+ *
+ *     @Override
+ *     public void removePermission(Player p, String permission) {
+ *         lp.getUserManager().modifyUser(p.getUniqueId(), u ->
+ *             u.data().remove(Node.builder(permission).build()));
+ *     }
+ *
+ *     @Override
+ *     public boolean hasPermission(Player p, String permission) {
+ *         return p.hasPermission(permission); // LuckPerms injects into Bukkit
+ *     }
+ *
+ *     @Override
+ *     public void addGroup(Player p, String group) {
+ *         lp.getUserManager().modifyUser(p.getUniqueId(), u ->
+ *             u.data().add(InheritanceNode.builder(group).build()));
+ *     }
+ *
+ *     @Override
+ *     public void removeGroup(Player p, String group) {
+ *         lp.getUserManager().modifyUser(p.getUniqueId(), u ->
+ *             u.data().remove(InheritanceNode.builder(group).build()));
+ *     }
+ *
+ *     @Override
+ *     public boolean hasGroup(Player p, String group) {
+ *         User u = lp.getUserManager().getUser(p.getUniqueId());
+ *         return u != null && u.getInheritedGroups(u.getQueryOptions())
+ *                 .stream().anyMatch(g -> g.getName().equalsIgnoreCase(group));
+ *     }
+ * }
+ * }
+ * + *

Menu usage

+ * + * Once a handler is registered, menus use permission primitives without + * caring which backing plugin is wired up: + * + *
{@code
+ * rules: [
+ *   { type: permission, value: "myserver.vip" }
+ * ]
+ * actions {
+ *   click: [
+ *     { type: givePermission, value: "myserver.reward.claimed" }
+ *     { type: addGroup,       value: "donor" }
+ *   ]
+ * }
+ * }
+ * + *

Threading

+ * + * All methods are called on the main server thread in response to inventory + * events. Getters ({@link #hasPermission}, {@link #hasGroup}) must be cheap + * — LuckPerms caches inheritance resolution, but custom backends should + * avoid blocking IO here. Mutators ({@link #addPermission}, + * {@link #addGroup}, …) may schedule the persistence step + * asynchronously provided the in-memory view updates before the next + * {@code has*} call. + * + * @see ru.abstractmenus.api.ProviderRegistry#registerPermissions + * @see EconomyHandler + * @see LevelHandler + */ +public interface PermissionsHandler { + + /** + * Grants {@code permission} to the player. Backs the + * {@code givePermission} menu action. + * + * @param player the player to grant; never {@code null}, always online + * @param permission the permission node (e.g. {@code "myserver.vip"}); + * never {@code null}, non-empty + * + * @implNote If the backend persists asynchronously, ensure the in-memory + * view updates synchronously so that a follow-up + * {@link #hasPermission(Player, String)} call in the same tick + * returns {@code true}. + */ + void addPermission(Player player, String permission); + + /** + * Revokes {@code permission} from the player. Backs the + * {@code removePermission} menu action. + * + *

If the player does not currently hold the permission the call is a + * no-op — revocation of an absent node is not an error. + * + * @param player the player to revoke; never {@code null}, always online + * @param permission the permission node to remove; never {@code null}, + * non-empty + */ + void removePermission(Player player, String permission); + + /** + * Returns {@code true} if the player holds {@code permission}. Backs the + * {@code permission} menu rule. + * + *

This is a pure predicate — implementations must not mutate + * state. Inherited permissions (via groups or wildcards) count as + * {@code true}. + * + * @param player the player being checked; never {@code null}, always + * online + * @param permission the permission node to test; never {@code null}, + * non-empty + * @return {@code true} if the player holds the permission directly or by + * inheritance, {@code false} otherwise + * + * @implNote Callers assume this is cheap — avoid blocking IO. + * Delegate to {@link Player#hasPermission(String)} when the + * backend injects into Bukkit's permission system (LuckPerms, + * PermissionsEx). + */ + boolean hasPermission(Player player, String permission); + + /** + * Adds the player to the named group. Backs the {@code addGroup} menu + * action. + * + * @param player the player to add; never {@code null}, always online + * @param group the group name; never {@code null}, non-empty, case + * sensitivity is backend-defined + * + * @implNote If the backend does not model groups (e.g. a permissions-only + * provider), treat the call as a no-op rather than throwing. + */ + void addGroup(Player player, String group); + + /** + * Removes the player from the named group. Backs the {@code removeGroup} + * menu action. + * + *

If the player is not currently in the group the call is a no-op. + * + * @param player the player to remove; never {@code null}, always online + * @param group the group name; never {@code null}, non-empty + */ + void removeGroup(Player player, String group); + + /** + * Returns {@code true} if the player is a member (direct or inherited) of + * the named group. Backs the {@code group} menu rule. + * + *

This is a pure predicate — implementations must not mutate + * state. + * + * @param player the player being checked; never {@code null}, always + * online + * @param group the group name to test; never {@code null}, non-empty + * @return {@code true} if the player is a member of the group directly or + * via group inheritance, {@code false} otherwise — including + * when the backend does not recognise the group + * + * @implNote Callers assume this is cheap — avoid blocking IO. + */ + boolean hasGroup(Player player, String group); + +} diff --git a/api/src/main/java/ru/abstractmenus/api/handler/PlaceholderHandler.java b/api/src/main/java/ru/abstractmenus/api/handler/PlaceholderHandler.java new file mode 100644 index 0000000..eb049f9 --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/handler/PlaceholderHandler.java @@ -0,0 +1,183 @@ +package ru.abstractmenus.api.handler; + +import org.bukkit.entity.Player; + +import java.util.List; + +/** + * Handler for placeholder substitution across every user-facing string a menu + * produces — item names, lore, titles, action arguments, rule values. + * + *

A {@code PlaceholderHandler} adapts a specific placeholder engine + * (PlaceholderAPI, MVdWPlaceholderAPI, …) to the contract AbstractMenus + * calls everywhere a string might contain {@code %name%}-style tokens: + * + *

    + *
  • {@link #replace(Player, String)} is invoked for every single-line + * string rendered into an inventory (display names, menu titles, + * action arguments);
  • + *
  • {@link #replace(Player, List)} is invoked for every multi-line string + * (item lore);
  • + *
  • {@link #replacePlaceholder(Player, String)} is invoked for individual + * placeholder resolution from code paths that already isolate the + * token — it is not the main entry point for + * menu rendering.
  • + *
+ * + *

Unlike economy or level handlers, placeholder substitution is pervasive: + * it runs on essentially every string the player sees. Implementations must + * be fast and tolerant of malformed input — never throw from the + * {@code replace} methods, return the original string instead. + * + *

Registration

+ * + * Although the placeholder handler lives in + * {@link ru.abstractmenus.api.ProviderRegistry} for API symmetry with the + * other sections, in practice it is registered once globally + * via {@link ru.abstractmenus.api.ProviderRegistry#registerPlaceholders} at + * {@link ru.abstractmenus.api.MenuExtension#onEnable} time, not selected + * per-action. Per-element provider selection is not meaningful for + * placeholders — the core engine (PAPI) is chain-of-responsibility by + * design. + * + *

Example — bridging PlaceholderAPI

+ * + * PAPI exposes a single facade that covers every registered expansion: + * + *
{@code
+ * public final class PapiPlaceholders implements PlaceholderHandler {
+ *
+ *     @Override
+ *     public String replacePlaceholder(Player p, String placeholder) {
+ *         return PlaceholderAPI.setPlaceholders(p, "%" + placeholder + "%");
+ *     }
+ *
+ *     @Override
+ *     public String replace(Player p, String str) {
+ *         if (str == null || str.isEmpty()) return str;
+ *         return PlaceholderAPI.setPlaceholders(p, str);
+ *     }
+ *
+ *     @Override
+ *     public List replace(Player p, List list) {
+ *         if (list == null || list.isEmpty()) return list;
+ *         return PlaceholderAPI.setPlaceholders(p, list);
+ *     }
+ *
+ *     @Override
+ *     public void registerAll() {
+ *         new AbstractMenusExpansion().register();
+ *     }
+ * }
+ * }
+ * + *

Menu usage

+ * + * Once a handler is registered, any string in any menu file may contain + * placeholders and they will be resolved per-viewer at render time: + * + *
{@code
+ * items {
+ *   stats {
+ *     material: BOOK
+ *     name: "&6Welcome, %player_name%"
+ *     lore: [
+ *       "&7Balance: &e%vault_eco_balance%"
+ *       "&7Level:   &a%player_level%"
+ *     ]
+ *   }
+ * }
+ * }
+ * + *

Threading

+ * + * All methods are called on the main server thread during inventory render. + * Because they run per-item per-tick while a menu is open, blocking IO here + * will freeze the server — PAPI expansions that hit a database must + * cache aggressively and serve stale values. The engine itself should never + * block. + * + * @see ru.abstractmenus.api.ProviderRegistry#registerPlaceholders + * @see EconomyHandler + */ +public interface PlaceholderHandler { + + /** + * Resolves a single placeholder token (without surrounding {@code %} + * characters) for the given player. + * + *

Used by code paths that already isolated the placeholder key. + * Prefer {@link #replace(Player, String)} for free-form strings — + * this method is not the main entry point for menu rendering. + * + * @param player the context player; never {@code null}, always + * online + * @param placeholder the placeholder key without the + * surrounding {@code %} characters + * (e.g. {@code "player_name"}); never {@code null} + * @return the resolved value, or the original token surrounded by + * {@code %} if the placeholder is unknown to the backend + * + * @implNote Implementations that wrap a token-by-token engine may simply + * delegate to {@link #replace(Player, String)} after wrapping + * the key in {@code %}. + */ + String replacePlaceholder(Player player, String placeholder); + + /** + * Resolves every placeholder in {@code str} for the given player. + * + *

This is the hot path for menu rendering: AbstractMenus calls it for + * display names, titles, and action arguments on every redraw. Never + * throw — return {@code str} unchanged if the backend errors. + * + * @param player the context player; never {@code null}, always online + * @param str the string to process; may be {@code null} or empty, in + * which case it is returned as-is + * @return the string with every recognised placeholder substituted; + * unknown placeholders are returned verbatim so the user sees + * {@code %missing%} rather than an empty slot + * + * @implNote Short-circuit on {@code null}/empty input before calling the + * backend — PAPI tolerates both but the call is not free. + */ + String replace(Player player, String str); + + /** + * Resolves every placeholder in each element of {@code list} for the + * given player. Used for item lore and other multi-line strings. + * + *

Implementations typically delegate to + * {@link #replace(Player, String)} per element, but may batch when the + * backend supports it. + * + * @param player the context player; never {@code null}, always online + * @param list the list to process; may be {@code null} or empty, in + * which case it is returned as-is + * @return a list with placeholders substituted. Implementations may + * return the same list instance (mutated) or a new list — + * callers must not assume either + * + * @implNote Never throw; if one element fails, log and keep processing + * the rest. + */ + List replace(Player player, List list); + + /** + * Registers AbstractMenus' own placeholders (menu-scoped variables, + * built-ins) with the backing engine. + * + *

Invoked once during + * {@link ru.abstractmenus.api.MenuExtension#onEnable} after the handler + * is wired up, so that other plugins querying PAPI can resolve + * AbstractMenus' placeholders too. Safe to call multiple times — + * implementations should guard against double-registration if the + * backend rejects duplicates. + * + * @implNote For PAPI, instantiate and {@code register()} your + * {@code PlaceholderExpansion}. For backends without a + * registration concept, treat this as a no-op. + */ + void registerAll(); + +} diff --git a/api/src/main/java/ru/abstractmenus/api/handler/SkinHandler.java b/api/src/main/java/ru/abstractmenus/api/handler/SkinHandler.java new file mode 100644 index 0000000..ef865eb --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/handler/SkinHandler.java @@ -0,0 +1,144 @@ +package ru.abstractmenus.api.handler; + +import org.bukkit.entity.Player; + +/** + * Handler for player skin operations invoked by skin-related menu actions. + * + *

A {@code SkinHandler} adapts a specific skin backend (SkinsRestorer, + * direct NMS {@code GameProfile} manipulation, …) to the contract + * AbstractMenus calls when a menu declares a skin-aware element: + * + *

    + *
  • the {@code setSkin} action calls + * {@link #setSkin(Player, String, String)} with a Mojang-style texture + * and signature pair;
  • + *
  • the {@code resetSkin} action calls {@link #resetSkin(Player)} to + * restore the player's original Mojang skin.
  • + *
+ * + *

Unlike the other handlers, there is no matching rule section — + * skins are applied, not queried, from menus. + * + *

Registration

+ * + * Multiple handlers may coexist — the highest-priority one is picked + * when a menu does not name a provider explicitly. Register yours via + * {@link ru.abstractmenus.api.ProviderRegistry#registerSkins} inside your + * addon's {@link ru.abstractmenus.api.MenuExtension#onEnable}. + * + *

When no skin handler is registered, {@code setSkin} and {@code resetSkin} + * actions become no-ops — skin mutation requires packet-level access + * that AbstractMenus does not ship with by default. + * + *

Example — bridging SkinsRestorer

+ * + * SkinsRestorer exposes a high-level facade that handles player re-spawn, + * packet re-sending, and persistence across sessions: + * + *
{@code
+ * public final class SkinsRestorerSkins implements SkinHandler {
+ *
+ *     private final SkinsRestorer sr;
+ *
+ *     public SkinsRestorerSkins(SkinsRestorer sr) { this.sr = sr; }
+ *
+ *     @Override
+ *     public void setSkin(Player p, String texture, String signature) {
+ *         SkinProperty prop = SkinProperty.of(texture, signature);
+ *         sr.getPlayerStorage().setSkinOfPlayer(p.getUniqueId(), prop);
+ *         sr.getSkinApplier(Player.class).applySkin(p, prop);
+ *     }
+ *
+ *     @Override
+ *     public void resetSkin(Player p) {
+ *         sr.getPlayerStorage().removeSkinOfPlayer(p.getUniqueId());
+ *         sr.getSkinApplier(Player.class).applySkin(p); // reapply Mojang skin
+ *     }
+ * }
+ * }
+ * + *

An alternative impl may talk to NMS directly by mutating the player's + * {@code GameProfile} properties and sending + * {@code PlayerInfoUpdatePacket}/respawn packets — but SkinsRestorer is + * the canonical provider because it handles the packet dance correctly. + * + *

Menu usage

+ * + * Once a handler is registered, menus can swap skins on click: + * + *
{@code
+ * items {
+ *   pirate {
+ *     material: PLAYER_HEAD
+ *     name: "&ePirate skin"
+ *     actions {
+ *       click: [
+ *         { type: setSkin, texture: "ewogICJ0aW1lc3RhbXA...", signature: "..." }
+ *       ]
+ *     }
+ *   }
+ *   reset {
+ *     material: BARRIER
+ *     name: "&cReset skin"
+ *     actions { click: [ { type: resetSkin } ] }
+ *   }
+ * }
+ * }
+ * + *

Threading

+ * + * All methods are called on the main server thread in response to inventory + * events. Applying a skin typically involves a brief packet burst + * (remove-player, add-player, respawn); SkinsRestorer already schedules this + * correctly. Implementations that hit an external texture-fetching service + * (e.g. mineskin.org) must do the HTTP call asynchronously + * and apply on the main thread — blocking here freezes every online + * player. + * + * @see ru.abstractmenus.api.ProviderRegistry#registerSkins + * @see EconomyHandler + */ +public interface SkinHandler { + + /** + * Applies the given texture/signature pair as the player's active skin. + * Backs the {@code setSkin} menu action. + * + *

The {@code texture} is the Base64-encoded JSON payload as returned + * by Mojang's session server (the {@code value} field of the + * {@code textures} property on a {@code GameProfile}). The + * {@code signature} is the matching Yggdrasil signature — without + * it, vanilla clients will reject the skin. + * + * @param player the player whose skin is being set; never {@code null}, + * always online + * @param texture the Base64-encoded texture payload; never {@code null}, + * never empty + * @param signature the matching Yggdrasil signature; never {@code null}, + * never empty + * + * @implNote Typical flow: update the player's {@code GameProfile} + * properties, send a {@code PlayerInfoUpdatePacket} with + * {@code REMOVE_PLAYER} then {@code ADD_PLAYER} actions to + * every viewer, and respawn the target player so their own + * client re-renders. SkinsRestorer does this for you. + */ + void setSkin(Player player, String texture, String signature); + + /** + * Resets the player's skin to their original Mojang-hosted skin. Backs + * the {@code resetSkin} menu action. + * + *

If the player never had a custom skin applied the call is a no-op. + * + * @param player the player whose skin is being reset; never {@code null}, + * always online + * + * @implNote Implementations that cache applied skins should clear the + * entry for this player so a subsequent {@code setSkin} call + * reflects the new value rather than a stale cache. + */ + void resetSkin(Player player); + +} diff --git a/api/src/main/java/ru/abstractmenus/api/inventory/Item.java b/api/src/main/java/ru/abstractmenus/api/inventory/Item.java new file mode 100644 index 0000000..b59eb5d --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/inventory/Item.java @@ -0,0 +1,131 @@ +package ru.abstractmenus.api.inventory; + +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.Map; + +/** + * A single declared slot-filler inside a {@link Menu} — the bridge between + * a HOCON item block and the Bukkit {@link ItemStack} shown to a viewer. + * + *

An item holds a map of named {@link ItemProperty ItemProperties} + * (material, display-name, lore, texture, glow, …) plus per-item rules + * and actions stored separately by the containing menu. When the menu is + * rendered, {@link #build(Player, Menu)} walks every property in registration + * order, letting each mutate the {@link ItemStack} / {@link org.bukkit.inventory.meta.ItemMeta}. + * + *

Addon authors do not implement this interface. Instead, + * contribute new {@link ItemProperty} types via + * {@link ru.abstractmenus.api.AbstractMenusApi#itemProperties()} — core + * will instantiate and attach them automatically when HOCON parsing encounters + * the matching key. + * + *

Menu usage

+ * + * Every top-level key inside an item block is parsed as an {@link ItemProperty}: + * + *
{@code
+ * items: [
+ *   {
+ *     slot: 4
+ *     material: PLAYER_HEAD    # PropMaterial
+ *     name: "&6%player_name%"  # PropName
+ *     lore: [ "&7Online", "" ] # PropLore
+ *     texture: "%player_name%" # PropTexture
+ *   }
+ * ]
+ * }
+ * + *

Threading

+ * + * Item construction runs on the main server thread inside menu render and + * refresh. Implementations should not block. + * + * @see ItemProperty + * @see Menu + */ +public interface Item extends Cloneable { + + /** + * Returns all attached properties keyed by their HOCON name + * ({@code "material"}, {@code "name"}, {@code "lore"}, …). + * + *

The returned map is the live backing store — mutating it + * affects subsequent {@link #build(Player, Menu)} calls. + * + * @return the live property map; never {@code null} + */ + Map getProperties(); + + /** + * Attaches a single property, replacing any existing entry under + * {@code key}. + * + * @param key HOCON-visible property name (e.g. {@code "lore"}) + * @param property the property instance to attach + */ + void addProperty(String key, ItemProperty property); + + /** + * Replaces the entire property map. + * + *

Used by serializers when re-loading an item from config. Normal code + * should prefer {@link #addProperty(String, ItemProperty)} / + * {@link #removeProperty(String)} to avoid wiping rules-relevant state. + * + * @param properties the new property map; never {@code null} + */ + void setProperties(Map properties); + + /** + * Detaches a property by key. + * + * @param key HOCON-visible property name + * @return the removed property, or {@code null} if none was attached under + * that key + */ + ItemProperty removeProperty(String key); + + /** + * Checks whether a raw {@link ItemStack} matches this item's declaration. + * + *

Used by item-requiring actions ({@code takeItem}) to find the slot + * holding the expected item in the player's inventory. Placeholders inside + * properties are resolved against {@code player} before comparison so + * player-specific names and textures match correctly. + * + * @param item the stack to compare against; {@code null} always yields + * {@code false} + * @param player the context player for placeholder resolution + * @return {@code true} if every material / meta aspect declared by this + * item matches {@code item} + */ + boolean isSimilar(ItemStack item, Player player); + + /** + * Materialises this item into a Bukkit {@link ItemStack} tailored for + * {@code player}. + * + *

Properties run in registration order: those flagged + * {@link ItemProperty#canReplaceMaterial()} run first so the stack has a + * valid {@link org.bukkit.inventory.meta.ItemMeta} by the time the rest + * mutate it. Placeholders inside property values are resolved against + * {@code player}. + * + * @param player the viewer — used for placeholder replacement + * @param menu the owning menu, used by properties that need to look up + * the activator or activation context; may be {@code null} + * when building outside an open session + * @return the rendered stack; never {@code null} + */ + ItemStack build(Player player, Menu menu); + + /** + * Returns a deep copy so per-viewer property tweaks don't leak to the + * shared menu template. + * + * @return an independent copy of this item + */ + Item clone(); +} diff --git a/api/src/main/java/ru/abstractmenus/api/inventory/ItemProperty.java b/api/src/main/java/ru/abstractmenus/api/inventory/ItemProperty.java new file mode 100644 index 0000000..36fc320 --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/inventory/ItemProperty.java @@ -0,0 +1,149 @@ +package ru.abstractmenus.api.inventory; + +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +/** + * A single mutator that contributes one aspect of a rendered {@link Item} + * — e.g. its material, display name, lore line, player-head texture or + * glow effect. + * + *

An item's HOCON block is parsed as a set of named properties: every + * top-level key ({@code material}, {@code name}, {@code lore}, …) is + * looked up in {@link ru.abstractmenus.api.AbstractMenusApi#itemProperties()}, + * deserialized and attached to the owning {@link Item}. At render time + * {@link Item#build(Player, Menu)} walks the resulting property map and + * invokes {@link #apply(ItemStack, ItemMeta, Player, Menu)} on each one so it + * can mutate the outgoing {@link ItemStack} / {@link ItemMeta}. + * + *

This is the primary extension point for addons. To contribute a new + * property, implement this interface, pair it with an inner + * {@link ru.abstractmenus.hocon.api.serialize.NodeSerializer NodeSerializer} + * and register both in your {@link ru.abstractmenus.api.MenuExtension#onEnable}. + * + *

Example — a {@code glow} property

+ * + *
{@code
+ * public final class PropGlow implements ItemProperty {
+ *
+ *     private final boolean glow;
+ *
+ *     public PropGlow(boolean glow) { this.glow = glow; }
+ *
+ *     @Override public boolean canReplaceMaterial() { return false; }
+ *     @Override public boolean isApplyMeta()        { return true;  }
+ *
+ *     @Override
+ *     public void apply(ItemStack item, ItemMeta meta, Player player, Menu menu) {
+ *         if (!glow) return;
+ *         meta.addEnchant(Enchantment.UNBREAKING, 1, true);
+ *         meta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
+ *     }
+ *
+ *     public static final class Serializer extends NodeSerializer {
+ *         // ... HOCON -> PropGlow
+ *     }
+ * }
+ *
+ * // registration:
+ * api.itemProperties().register("glow", PropGlow.class, new PropGlow.Serializer(), this);
+ * }
+ * + *

Menu usage

+ * + * Once registered, the property is available by key inside any item block: + * + *
{@code
+ * items: [
+ *   {
+ *     slot: 13
+ *     material: DIAMOND_SWORD
+ *     name: "&bLegendary Blade"
+ *     glow: true
+ *   }
+ * ]
+ * }
+ * + *

Threading

+ * + * {@link #apply(ItemStack, ItemMeta, Player, Menu)} runs on the main server + * thread during item rendering — implementations may touch the Bukkit + * API freely but must not block. Long-running work (HTTP texture fetches, + * database reads) should be dispatched through {@code FoliaLib} and cached; + * {@code apply} itself should only read the cache. + * + * @see Item + * @see Menu + * @see ru.abstractmenus.api.AbstractMenusApi#itemProperties() + */ +public interface ItemProperty { + + /** + * Whether this property overwrites the item's {@link org.bukkit.Material}. + * + *

Material-replacing properties (e.g. {@code material}, {@code head}, + * {@code headTexture}) run before all others so the stack + * has a valid {@link ItemMeta} by the time the rest mutate it — + * Bukkit otherwise throws away meta when the material changes. + * + * @return {@code true} if {@link #apply(ItemStack, ItemMeta, Player, Menu)} + * may swap the stack's material, {@code false} for meta-only + * mutators + * + * @apiNote Return {@code false} unless you really do reassign the material. + * The flag only controls ordering; returning {@code true} + * spuriously forces the rest of the pipeline to run against a + * stack whose meta may have been reset. + */ + boolean canReplaceMaterial(); + + /** + * Whether the {@link ItemMeta} argument passed to + * {@link #apply(ItemStack, ItemMeta, Player, Menu)} should be written back + * onto the stack automatically after {@code apply} returns. + * + *

Return {@code true} when your property only mutates meta fields + * (display name, lore, flags, enchants) and lets the caller commit the + * meta for you. Return {@code false} when you need to call + * {@link ItemStack#setItemMeta(ItemMeta)} yourself — typically + * because you swap to a different meta type or tweak the stack in ways + * the shared commit would overwrite. + * + * @return {@code true} if the framework should call + * {@code item.setItemMeta(meta)} on your behalf after + * {@code apply} returns + * + * @apiNote Returning {@code true} and also calling + * {@code item.setItemMeta(...)} manually inside {@code apply} + * leads to lost writes — the framework's follow-up + * {@code setItemMeta} overwrites yours. Pick one. + */ + boolean isApplyMeta(); + + /** + * Applies this property to the in-flight item stack. + * + *

Called once per render per viewer, in registration order relative to + * the item's other properties. Placeholders inside property values should + * be resolved against {@code player} before use so per-viewer content + * (names, textures, balances) renders correctly. + * + * @param item the stack being rendered — mutate freely, never + * {@code null}. Prefer mutating {@code meta} unless you + * specifically need to touch the stack itself. + * @param meta the stack's current {@link ItemMeta}; write display-name, + * lore, flags, enchants here. Committed automatically iff + * {@link #isApplyMeta()} returns {@code true}. + * @param player the viewer for whom the item is being built; never + * {@code null} and always online. Use for placeholder + * resolution and permission-aware rendering. + * @param menu the owning menu; may be {@code null} when an item is built + * outside an open session (e.g. for {@code isSimilar} + * comparisons). Use to read the activator / activation + * context via {@link Menu#getActivatedBy()} and + * {@link Menu#getContext()}. + */ + void apply(ItemStack item, ItemMeta meta, Player player, Menu menu); + +} diff --git a/api/src/main/java/ru/abstractmenus/api/inventory/Menu.java b/api/src/main/java/ru/abstractmenus/api/inventory/Menu.java new file mode 100644 index 0000000..b0fba0f --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/inventory/Menu.java @@ -0,0 +1,290 @@ +package ru.abstractmenus.api.inventory; + +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.InventoryHolder; +import ru.abstractmenus.api.Activator; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +/** + * A loaded AbstractMenus GUI — a Bukkit {@link InventoryHolder} enriched with + * activators, rules and actions declared in a HOCON menu file. + * + *

A menu is the top-level building block of the plugin. It owns a fixed-size + * grid of {@link Item items}, one or more {@link Activator activators} that decide + * how the menu is triggered (commands, item-use, proximity, …), and a + * lifecycle shared by every viewer: + * + *

    + *
  1. an activator fires — the plugin resolves the target menu and calls + * {@link #open(Player)};
  2. + *
  3. the menu builds an {@link org.bukkit.inventory.Inventory} for the player, + * asking each {@link Item} to produce an {@link org.bukkit.inventory.ItemStack};
  4. + *
  5. while open, {@link #update(Player)} ticks on a fixed interval so + * time-sensitive properties ({@code placeholder}, {@code texture}…) + * refresh; {@link #refresh(Player)} and {@link #refreshItem(Slot, Player)} + * can be called explicitly;
  6. + *
  7. inventory clicks are routed to {@link #click(int, Player, ClickType)} + * which matches the slot against the menu's items and runs their actions;
  8. + *
  9. {@link #close(Player)} dismisses the inventory and releases per-viewer + * state.
  10. + *
+ * + *

Core ships three concrete implementations — a standard + * {@code SimpleMenu}, an {@code AnimatedMenu} with frame-based animations, and a + * {@code GeneratedMenu} whose layout is produced dynamically from a matrix. + * Addons normally do not implement this interface directly + * — they contribute new {@link ru.abstractmenus.api.Action Actions}, + * {@link ru.abstractmenus.api.Rule Rules}, {@link ItemProperty item properties} + * or {@link Activator activators} that existing menu types consume. + * + *

Opening a menu programmatically

+ * + * Never call {@link #open(Player)} directly from arbitrary code — go + * through the API so activator wiring and context propagation are preserved: + * + *
{@code
+ * AbstractMenusApi api = AbstractMenusApi.get();
+ * Menu shop = api.getMenuByName("shop");
+ * api.openMenu(player, shop);
+ * }
+ * + *

Menu usage

+ * + * A minimal HOCON menu file producing this interface: + * + *
{@code
+ * title: "&6Shop"
+ * size: 27
+ * activators: [
+ *   { type: command, command: shop }
+ * ]
+ * items: [
+ *   {
+ *     slot: 13
+ *     material: DIAMOND
+ *     name: "&bBuy diamond"
+ *     rules:   [ { type: hasMoney, amount: 100 } ]
+ *     actions {
+ *       click: [
+ *         { type: takeMoney, amount: 100 }
+ *         { type: giveItem,  material: DIAMOND }
+ *       ]
+ *     }
+ *   }
+ * ]
+ * }
+ * + *

Threading

+ * + * Every method on this interface is invoked on the main server thread: lifecycle + * calls come from {@code MenuManager}'s 20-tick update loop or from Bukkit + * inventory events. Implementations may touch the Bukkit API freely but must + * not block — long-running work should be dispatched through + * {@code FoliaLib} and its result written back on the main thread. + * + * @see Item + * @see ItemProperty + * @see Slot + * @see Activator + * @see ru.abstractmenus.api.AbstractMenusApi#openMenu(Player, Menu) + */ +public interface Menu extends InventoryHolder, Cloneable { + + /** + * Returns every {@link Activator} declared under this menu's + * {@code activators} HOCON block. + * + *

The list is iteration-ordered to match the config file. An empty list + * means the menu can only be opened programmatically via + * {@link ru.abstractmenus.api.AbstractMenusApi#openMenu(Player, Menu)}. + * + * @return the declared activators; never {@code null} + */ + List getActivators(); + + /** + * Returns the activator that actually triggered this open session, + * or empty when the menu was opened programmatically. + * + *

Populated by the core when it invokes + * {@link ru.abstractmenus.api.AbstractMenusApi#openMenu(Activator, Object, Player, Menu)}; + * useful for actions that want to inspect the trigger (e.g. the command + * arguments, the clicked block, …). + * + * @return the firing activator wrapped in an {@link Optional}, empty for + * programmatic opens + */ + Optional getActivatedBy(); + + /** + * Returns the activation context object attached at open-time. + * + *

Semantics depend on the firing {@link Activator}: a + * {@code PlayerInteractEvent} for {@code interact}, an {@code Entity} for + * {@code npcClick}, a {@code Block} for {@code blockClick}, and so on. + * Used by {@code ValueExtractor}s to resolve context-scoped placeholders. + * + * @return the activation context wrapped in an {@link Optional}, empty for + * programmatic opens + * + * @see ru.abstractmenus.api.ValueExtractor + */ + Optional getContext(); + + /** + * Looks up an already-rendered item at the given raw inventory slot. + * + *

This reads the materialized snapshot displayed to the current viewer + * — not the declaration order from the HOCON file. Returns the + * backing {@link Item} or {@code null} if the slot is empty or outside the + * menu bounds. + * + * @param slot inventory slot index; {@code 0 .. getSize() - 1} + * @return the item at that slot, or {@code null} if none + */ + Item getItem(int slot); + + /** + * Returns every item declared under the menu's {@code items} block. + * + *

Declaration order is preserved; items with matrix / range slots count + * once regardless of how many slots they occupy. + * + * @return all declared items; never {@code null} + */ + Collection getItems(); + + /** + * Returns the inventory size, always a multiple of {@code 9}. + * + * @return the current menu size in slots + */ + int getSize(); + + /** + * Resizes the menu. + * + *

Only takes effect for subsequent {@link #open(Player)} calls — + * already-open viewers keep the old inventory until they close and reopen. + * + * @param size new size; must be a multiple of {@code 9} and within Bukkit's + * inventory limits + */ + void setSize(int size); + + /** + * Opens this menu for {@code player}, evaluating top-level rules first. + * + *

Prefer {@link ru.abstractmenus.api.AbstractMenusApi#openMenu(Player, Menu)} + * so the core can wire an activator and context for you — direct + * calls to this method skip that bookkeeping. + * + * @param player the viewer; must be online + * @return {@code true} if the inventory was shown, {@code false} when a + * top-level rule rejected the open + * + * @implNote Core impls dispatch the returned {@code boolean} to the caller + * of {@code AbstractMenusApi.openMenu} so callers can react to + * rule-denied opens. + */ + boolean open(Player player); + + /** + * Rebuilds every item for {@code player} and pushes the new stacks into the + * open inventory. + * + *

Called automatically on the 20-tick update cycle for time-sensitive + * properties and on activator-driven events; invoke manually after mutating + * a variable that an item reads. + * + * @param player the viewer whose inventory to refresh + */ + void refresh(Player player); + + /** + * Rebuilds a single item and writes it back to the inventory. + * + *

Preferred over {@link #refresh(Player)} when only one slot changed, + * to avoid re-running every item's properties and rules. + * + * @param slot the slot(s) to refresh — a {@link Slot} may resolve + * to multiple inventory indices + * @param player the viewer whose inventory to update + */ + void refreshItem(Slot slot, Player player); + + /** + * Temporarily overrides a slot's {@link Item} without re-running the menu + * pipeline. + * + *

The override lives only in this viewer's session — the next + * {@link #refresh(Player)} or reopen restores the declared item. + * + * @param slot target slot(s) + * @param item the item to display, or {@code null} to clear + * @param player the viewer receiving the override + */ + void setItem(Slot slot, Item item, Player player); + + /** + * Per-tick update hook driven by {@code MenuManager}'s 20-tick loop. + * + *

Default impls call {@link #refresh(Player)} when the menu declares an + * {@code updateInterval}. Usually not invoked by addon code. + * + * @param player the viewer whose open session to tick + */ + void update(Player player); + + /** + * Closes this menu for {@code player} and also closes their inventory. + * + *

Shorthand for {@code close(player, true)}. + * + * @param player the viewer + */ + default void close(Player player) { + close(player, true); + } + + /** + * Releases per-viewer state and optionally closes the open inventory. + * + *

Pass {@code false} when the caller is about to open another menu + * — Bukkit fires a spurious close event whenever the inventory + * switches, and the two-argument overload lets core suppress duplicate + * bookkeeping. + * + * @param player the viewer + * @param closeInventory whether to also call {@link Player#closeInventory()} + */ + void close(Player player, boolean closeInventory); + + /** + * Dispatches a click event to the item bound to {@code slot}. + * + *

Runs the item's rules first; only if every rule passes does the item's + * matching click-action list fire. Invoked by core's inventory listener + * — addon code rarely calls this directly. + * + * @param slot raw inventory slot index from the click event + * @param player the clicker + * @param type left / right / shift / number-key … + */ + void click(int slot, Player player, ClickType type); + + /** + * Returns a deep copy suitable for per-viewer use. + * + *

Menus are loaded once from disk and cloned per open session so + * per-viewer state (overridden items, generated layouts) doesn't leak + * between players. + * + * @return an independent copy of this menu + */ + Menu clone(); +} diff --git a/api/src/main/java/ru/abstractmenus/api/inventory/Slot.java b/api/src/main/java/ru/abstractmenus/api/inventory/Slot.java new file mode 100644 index 0000000..24a822f --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/inventory/Slot.java @@ -0,0 +1,78 @@ +package ru.abstractmenus.api.inventory; + +import java.util.function.Consumer; + +/** + * An addressable set of one or more inventory slot indices inside a + * {@link Menu}. + * + *

Every item in a HOCON menu is bound to exactly one {@code Slot}. A slot + * may resolve to a single index ({@link ru.abstractmenus.api.inventory.slot.SlotIndex}), + * to {@code (row, col)} coordinates + * ({@link ru.abstractmenus.api.inventory.slot.SlotPos}), to a contiguous + * inclusive range ({@link ru.abstractmenus.api.inventory.slot.SlotRange}), or + * to every index matched by a 2D character mask + * ({@link ru.abstractmenus.api.inventory.slot.SlotMatrix}). The {@link Slot} + * interface itself is the common view — callers iterate resolved + * indices via {@link #getSlots(Consumer)} without caring which concrete + * variant produced them. + * + *

Example — iterating every slot an item occupies

+ * + *
{@code
+ * Slot slot = item.getSlot();
+ * slot.getSlots(i -> inventory.setItem(i, stack));
+ * }
+ * + *

Menu usage

+ * + * The {@code slot} key inside an item block accepts any of the variants, and + * the HOCON parser picks the matching {@code Slot} implementation: + * + *
{@code
+ * items: [
+ *   { slot: 13,            material: DIAMOND }          # SlotIndex
+ *   { slot: "2,4",         material: EMERALD }          # SlotPos
+ *   { slot: "10-16",       material: GLASS_PANE }       # SlotRange
+ *   {
+ *     slot {
+ *       matrix: [
+ *         "XXXXXXXXX",
+ *         "X       X",
+ *         "XXXXXXXXX"
+ *       ]
+ *       symbol: X
+ *     }
+ *     material: BLACK_STAINED_GLASS_PANE                # SlotMatrix
+ *   }
+ * ]
+ * }
+ * + * @see ru.abstractmenus.api.inventory.slot.SlotIndex + * @see ru.abstractmenus.api.inventory.slot.SlotPos + * @see ru.abstractmenus.api.inventory.slot.SlotRange + * @see ru.abstractmenus.api.inventory.slot.SlotMatrix + * @see Item + */ +public interface Slot { + + /** + * Feeds every raw inventory index this {@code Slot} resolves to into + * {@code indexCb}, in an implementation-defined order. + * + *

Single-index variants invoke the callback once; ranges and matrices + * invoke it once per matched cell. Indices are zero-based and bounded by + * the owning menu's {@link Menu#getSize()}; callers are responsible for + * clamping against the actual {@link org.bukkit.inventory.Inventory} size + * if the menu was resized after parsing. + * + * @param indexCb consumer invoked once per resolved index; never + * {@code null} + * + * @implNote Implementations must not hold thread state between + * invocations — the callback may be invoked synchronously + * from multiple render passes on the main server thread. + */ + void getSlots(Consumer indexCb); + +} diff --git a/api/src/main/java/ru/abstractmenus/api/inventory/slot/SlotIndex.java b/api/src/main/java/ru/abstractmenus/api/inventory/slot/SlotIndex.java new file mode 100644 index 0000000..57d620d --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/inventory/slot/SlotIndex.java @@ -0,0 +1,84 @@ +package ru.abstractmenus.api.inventory.slot; + +import ru.abstractmenus.api.inventory.Slot; + +import java.util.function.Consumer; + +/** + * A {@link ru.abstractmenus.api.inventory.Slot} variant that addresses a + * single inventory cell by its absolute zero-based index (0 … 53). + * + *

Use {@code SlotIndex} when you know the exact cell number, or when you + * want the engine to auto-place the item in the first available cell. + * For human-readable {@code (column, row)} addressing prefer + * {@link SlotPos}; for a contiguous block of cells use {@link SlotRange}; + * for a 2D character-mask pattern use {@link SlotMatrix}. + * + *

The special value {@code -1} instructs the renderer to insert the item + * into the first free slot of the inventory rather than a fixed position. + * + *

HOCON examples

+ * + *
{@code
+ * # absolute index
+ * slot: 13
+ *
+ * # last slot of a 6-row inventory
+ * slot: 53
+ *
+ * # auto-insert into first free cell
+ * slot: -1
+ * }
+ * + * @see SlotPos + * @see SlotRange + * @see SlotMatrix + * @see ru.abstractmenus.api.inventory.Slot + */ +public class SlotIndex implements Slot { + + private final int slot; + + /** + * Creates a {@code SlotIndex} for the given absolute inventory index. + * + * @param slot zero-based slot index (0 … 53), or {@code -1} to + * auto-insert into the first free cell + */ + public SlotIndex(int slot) { + this.slot = slot; + } + + /** + * Returns the raw slot index stored in this instance. + * + * @return the zero-based index, or {@code -1} for auto-insert mode + */ + public int getIndex() { + return slot; + } + + /** + * Invokes {@code indexCb} exactly once with the stored slot index. + * + * @param indexCb consumer to receive the resolved index; never {@code null} + * + * @implNote The callback is always invoked synchronously and exactly once, + * regardless of whether the index is {@code -1}. + */ + @Override + public void getSlots(Consumer indexCb) { + indexCb.accept(slot); + } + + /** + * Factory shortcut — equivalent to {@code new SlotIndex(index)}. + * + * @param index zero-based slot index or {@code -1} for auto-insert + * @return a new {@code SlotIndex} wrapping {@code index} + */ + public static SlotIndex of(int index) { + return new SlotIndex(index); + } + +} diff --git a/api/src/main/java/ru/abstractmenus/api/inventory/slot/SlotMatrix.java b/api/src/main/java/ru/abstractmenus/api/inventory/slot/SlotMatrix.java new file mode 100644 index 0000000..7b44cb9 --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/inventory/slot/SlotMatrix.java @@ -0,0 +1,76 @@ +package ru.abstractmenus.api.inventory.slot; + +import ru.abstractmenus.api.inventory.Slot; + +import java.util.function.Consumer; + +/** + * A {@link ru.abstractmenus.api.inventory.Slot} variant that selects inventory + * cells via a 2D character mask, making complex border and pattern layouts + * readable directly in HOCON. + * + *

Each string in the mask represents one inventory row. Any character other + * than {@code '-'} marks a cell as selected; {@code '-'} leaves the + * cell empty. The HOCON parser pre-resolves each selected character to its + * zero-based absolute index and passes the resulting array to this constructor + * — callers never deal with the raw strings at runtime. + * + *

Use {@code SlotMatrix} for irregular shapes such as borders, cross + * patterns, or icon clusters. For simpler needs prefer {@link SlotIndex} + * (single cell), {@link SlotPos} (column/row pair), or {@link SlotRange} + * (contiguous strip). + * + *

HOCON example

+ * + *
{@code
+ * # border frame in a 3-row menu (rows 0-2, 9 columns each)
+ * slot: [
+ *   "xxxxxxxxx",
+ *   "x-------x",
+ *   "xxxxxxxxx"
+ * ]
+ * }
+ * + * @see SlotIndex + * @see SlotPos + * @see SlotRange + * @see ru.abstractmenus.api.inventory.Slot + */ +public class SlotMatrix implements Slot { + + private final Integer[] slots; + + /** + * Creates a {@code SlotMatrix} from a pre-resolved array of absolute slot + * indices. + * + *

The array is produced by the HOCON deserializer, which converts each + * non-{@code '-'} character in the mask rows to its zero-based inventory + * index. Direct construction is only needed in tests or programmatic + * menu builders. + * + * @param slots ordered array of zero-based slot indices to select; + * must not be {@code null} + */ + public SlotMatrix(Integer[] slots) { + this.slots = slots; + } + + /** + * Passes every pre-resolved slot index to {@code indexCb} in the order + * they appear in the original mask (left-to-right, top-to-bottom). + * + * @param indexCb consumer invoked once per selected cell; + * never {@code null} + * + * @implNote Iteration order mirrors the HOCON mask: cells are visited + * row by row, left to right. The number of invocations equals + * the number of non-{@code '-'} characters in the original mask. + */ + @Override + public void getSlots(Consumer indexCb) { + for (int slot : slots) { + indexCb.accept(slot); + } + } +} diff --git a/api/src/main/java/ru/abstractmenus/api/inventory/slot/SlotPos.java b/api/src/main/java/ru/abstractmenus/api/inventory/slot/SlotPos.java new file mode 100644 index 0000000..a7967a1 --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/inventory/slot/SlotPos.java @@ -0,0 +1,66 @@ +package ru.abstractmenus.api.inventory.slot; + +import ru.abstractmenus.api.inventory.Slot; + +import java.util.function.Consumer; + +/** + * A {@link ru.abstractmenus.api.inventory.Slot} variant that addresses a + * single inventory cell via a human-readable {@code (column, row)} pair. + * + *

Both axes are 1-based: column 1 is the leftmost column and + * row 1 is the top row. The pair is converted to a zero-based index via + * {@code (row - 1) * 9 + (col - 1)}, so {@code (1, 1)} maps to + * index 0 and {@code (9, 6)} maps to index 53. + * + *

Prefer {@code SlotPos} over {@link SlotIndex} when the column/row + * position is more readable than the raw number. For a contiguous block + * of cells use {@link SlotRange}; for a 2D character-mask use + * {@link SlotMatrix}. + * + *

HOCON examples

+ * + *
{@code
+ * # inline "col,row" string -- column 5, row 2 (index 13)
+ * slot: "5, 2"
+ *
+ * # object form
+ * slot { x: 5, y: 2 }
+ * }
+ * + * @see SlotIndex + * @see SlotRange + * @see SlotMatrix + * @see ru.abstractmenus.api.inventory.Slot + */ +public class SlotPos implements Slot { + + private final int x; + private final int y; + + /** + * Creates a {@code SlotPos} from 1-based column and row coordinates. + * + * @param x column index, 1 … 9 (left to right) + * @param y row index, 1 … 6 (top to bottom) + */ + public SlotPos(int x, int y) { + this.x = x; + this.y = y; + } + + /** + * Converts the stored {@code (column, row)} pair to a zero-based absolute + * index and passes it to {@code indexCb}. + * + * @param indexCb consumer to receive the resolved index; never {@code null} + * + * @implNote The conversion formula is {@code (y - 1) * 9 + (x - 1)}, + * assuming a standard 9-column chest inventory layout. + */ + @Override + public void getSlots(Consumer indexCb) { + indexCb.accept((y-1) * 9 + (x-1)); + } + +} diff --git a/api/src/main/java/ru/abstractmenus/api/inventory/slot/SlotRange.java b/api/src/main/java/ru/abstractmenus/api/inventory/slot/SlotRange.java new file mode 100644 index 0000000..889c4cc --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/inventory/slot/SlotRange.java @@ -0,0 +1,71 @@ +package ru.abstractmenus.api.inventory.slot; + +import ru.abstractmenus.api.inventory.Slot; + +import java.util.function.Consumer; + +/** + * A {@link ru.abstractmenus.api.inventory.Slot} variant that fills every cell + * in a contiguous, inclusive range of absolute slot indices. + * + *

Use {@code SlotRange} to paint a strip of items across a row or a span of + * cells without enumerating each index individually. For a single cell use + * {@link SlotIndex}; for {@code (column, row)} addressing use {@link SlotPos}; + * for a 2D character-mask pattern use {@link SlotMatrix}. + * + *

The {@code min} bound is silently clamped to {@code 0} if a negative value + * is supplied. The {@code max} bound is used as-is; callers should ensure it + * does not exceed the owning menu's {@link ru.abstractmenus.api.inventory.Menu#getSize()} + * minus 1. + * + *

HOCON examples

+ * + *
{@code
+ * # fill the first row (slots 0-8)
+ * slot: "0-8"
+ *
+ * # border band across slots 10-16
+ * slot: "10-16"
+ * }
+ * + * @see SlotIndex + * @see SlotPos + * @see SlotMatrix + * @see ru.abstractmenus.api.inventory.Slot + */ +public class SlotRange implements Slot { + + private final int min; + private final int max; + + /** + * Creates a {@code SlotRange} spanning [{@code min}, {@code max}] inclusive. + * + * @param min lower bound, zero-based; clamped to {@code 0} if negative + * @param max upper bound, zero-based, inclusive; must be ≥ {@code min} + * after clamping + */ + public SlotRange(int min, int max) { + this.min = Math.max(min, 0); + this.max = max; + } + + /** + * Iterates every index in the range [{@code min}, {@code max}] inclusive + * and passes each to {@code indexCb}. + * + * @param indexCb consumer invoked once per slot in ascending order; + * never {@code null} + * + * @implNote The callback is invoked {@code max - min + 1} times in + * strictly ascending order, starting from the clamped + * {@code min} value. + */ + @Override + public void getSlots(Consumer indexCb) { + for (int i = min; i <= max; i++) { + indexCb.accept(i); + } + } + +} diff --git a/api/src/main/java/ru/abstractmenus/api/text/Colors.java b/api/src/main/java/ru/abstractmenus/api/text/Colors.java new file mode 100644 index 0000000..c711f4e --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/text/Colors.java @@ -0,0 +1,243 @@ +package ru.abstractmenus.api.text; + +import org.bukkit.ChatColor; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Utility for converting user-facing color notation to Minecraft section-code strings. + * + *

{@code Colors} is the single entry point for all text colorization in AbstractMenus. + * Every menu title, item display name, lore line, action message, and rule feedback string + * passes through {@link #of(String)} before being handed to the Bukkit API. Two + * transformations are applied, depending on server capabilities: + * + *

    + *
  • Legacy {@code &}-codes — any {@code &} followed by a + * recognized Minecraft color/format character ({@code 0}–{@code 9}, + * {@code a}–{@code f}, {@code k}–{@code r}) is translated to the + * corresponding {@code §} (section-sign) sequence via + * {@link ChatColor#translateAlternateColorCodes}. This works on all server + * versions.
  • + *
  • Hex RGB codes — when the server ships BungeeCord's + * {@code net.md_5.bungee.api.ChatColor#of(String)} (1.16+), the pattern + * {@code <#RRGGBB>} (e.g. {@code <#FF0000>}) is additionally recognised and + * expanded to the BungeeCord hex-color sequence before legacy translation + * runs.
  • + *
+ * + *

The active strategy ({@link SimpleReplacer} vs {@link RgbReplacer}) is chosen once + * at startup by {@link #init(boolean)} and stored in {@link #replacer}. All subsequent + * calls to {@code of*} methods are lock-free reads of that field. + * + *

Example

+ * + *
{@code
+ * // Input  : "&aHello <#FF0000>World"
+ * // Output : "§aHello §x§F§F§0§0§0§0World"
+ * String colored = Colors.of("&aHello <#FF0000>World");
+ * }
+ * + *

Performance

+ * + *

When hex support is active, {@link RgbReplacer} processes each hex token by + * rebuilding the entire string — the cost is {@code O(n × k)} where {@code n} + * is the string length and {@code k} is the number of hex tokens. {@code Colors.of} is + * called on every user-facing string at display time (titles, lore…), so menus + * with many hex tokens in long lore lists will allocate proportionally. Pre-colorize + * strings at load time rather than on every inventory open to avoid repeated allocation. + * + * @see ChatColor#translateAlternateColorCodes(char, String) + * @see net.md_5.bungee.api.ChatColor + */ +public class Colors { + + private static final char COLOR_PREFIX = '&'; + private static Replacer replacer; + + /** + * Selects and initializes the color-replacement strategy. + * + *

Must be called exactly once during plugin startup, before any call to + * {@link #of(String)}. AbstractMenus calls this internally; addon code must + * not call it again. + * + *

The decision tree: + *

    + *
  1. If {@code replaceRgb} is {@code false} — always use the legacy + * {@code &}-only {@link SimpleReplacer}, regardless of server version.
  2. + *
  3. If {@code replaceRgb} is {@code true} and the server provides + * {@code net.md_5.bungee.api.ChatColor#of(String)} (detected via reflection) + * — activate {@link RgbReplacer} which handles both {@code <#RRGGBB>} + * and {@code &}-codes.
  4. + *
  5. Otherwise — fall back to {@link SimpleReplacer}.
  6. + *
+ * + * @param replaceRgb {@code true} to enable {@code <#RRGGBB>} hex expansion when the + * server supports it; {@code false} to restrict to legacy codes only + * + * @implNote RGB capability is probed by loading + * {@code net.md_5.bungee.api.ChatColor} and reflectively looking up the + * {@code of(String)} method. Any {@link Throwable} is silently caught, + * treating the server as legacy-only. + */ + public static void init(boolean replaceRgb) { + if (isSupportRgb() && replaceRgb) { + replacer = new RgbReplacer(); + } else { + replacer = new SimpleReplacer(); + } + } + + /** + * Translates all color codes in {@code line} to Minecraft section-sign sequences. + * + *

The exact transformations applied depend on which strategy was selected by + * {@link #init(boolean)}: + *

    + *
  • {@link SimpleReplacer} — translates {@code &X} legacy codes only.
  • + *
  • {@link RgbReplacer} — first expands {@code <#RRGGBB>} hex tokens, then + * translates {@code &X} legacy codes.
  • + *
+ * + * @param line the raw input string, possibly containing {@code &}-codes and/or + * {@code <#RRGGBB>} tokens; may be empty but must not be {@code null} + * @return a new string with all recognized color codes replaced by their + * section-sign equivalents, ready for use in Bukkit display names, lore, + * titles, and action-bar messages + */ + public static String of(String line) { + return replacer.replace(line); + } + + /** + * Translates color codes in every element of {@code list} in-place. + * + *

Each element is replaced by the result of {@link #of(String)}. The list + * itself is mutated and then returned — no defensive copy is made. + * + * @param list a mutable list of raw strings; must not be {@code null} + * @return the same {@code list} instance with all elements colorized + */ + public static List ofList(List list){ + for(int i = 0; i < list.size(); i++){ + list.set(i, of(list.get(i))); + } + + return list; + } + + /** + * Translates color codes in every element of {@code array} in-place. + * + *

Each element is replaced by the result of {@link #of(String)}. The array + * itself is mutated and then returned — no defensive copy is made. + * + * @param array a mutable array of raw strings; must not be {@code null} + * @return the same {@code array} reference with all elements colorized + */ + public static String[] ofArr(String[] array){ + for (int i = 0; i < array.length; i++){ + array[i] = of(array[i]); + } + return array; + } + + /** + * Returns {@code true} if the running server provides BungeeCord's + * {@code net.md_5.bungee.api.ChatColor#of(String)} method, which is required + * for hex-color expansion. + * + * @return {@code true} when hex RGB support is available; {@code false} on legacy + * servers or in environments where BungeeCord classes are absent + * + * @implNote Uses {@link Class#forName(String)} and + * {@link Class#getDeclaredMethod(String, Class[])} to probe at runtime. + * Any {@link Throwable} (including {@link NoClassDefFoundError}) is caught + * and treated as "not supported". + */ + private static boolean isSupportRgb() { + try { + Class.forName("net.md_5.bungee.api.ChatColor") + .getDeclaredMethod("of", String.class); + return true; + } catch (Throwable t) { + return false; + } + } + + /** + * Internal strategy for string color replacement. + * + *

Implementations are selected once by {@link Colors#init(boolean)} and must be + * stateless — the single shared instance is called concurrently. + */ + private interface Replacer { + /** + * Translates all recognized color codes in {@code input}. + * + * @param input raw string; must not be {@code null} + * @return colorized string + */ + String replace(String input); + } + + /** + * {@link Replacer} that handles only legacy {@code &X} color codes. + * + *

Used on servers that predate 1.16 or when hex support is explicitly disabled + * via {@link Colors#init(boolean) Colors.init(false)}. + */ + private static class SimpleReplacer implements Replacer { + + @Override + public String replace(String input) { + return ChatColor.translateAlternateColorCodes(COLOR_PREFIX, input); + } + } + + /** + * {@link Replacer} that expands {@code <#RRGGBB>} hex tokens before translating + * legacy {@code &X} codes. + * + *

Hex tokens are matched by {@link #PATTERN} and replaced with the BungeeCord + * section-sign sequence produced by + * {@code net.md_5.bungee.api.ChatColor#of(String)}. After all tokens are expanded, + * {@link ChatColor#translateAlternateColorCodes} handles the remaining {@code &X} + * codes. + * + * @implNote Each hex replacement rebuilds the full string and resets the + * {@link Matcher}, giving {@code O(n × k)} allocation where + * {@code n} is the string length and {@code k} is the number of hex + * tokens. Strings should be colorized once at load time rather than on + * every display to keep hot paths allocation-free. + */ + private static class RgbReplacer implements Replacer { + + /** + * Matches hex color tokens in the form {@code <#RRGGBB>}, case-insensitive. + * Captures exactly six hexadecimal digits between {@code <#} and {@code >}. + */ + private static final Pattern PATTERN = Pattern.compile("<#([A-Fa-f0-9]){6}>"); + + @Override + public String replace(String input) { + Matcher matcher = PATTERN.matcher(input); + + while (matcher.find()) { + String group = matcher.group(); + net.md_5.bungee.api.ChatColor hexColor = net.md_5.bungee.api.ChatColor.of(group + .substring(1, group.length() - 1)); + String before = input.substring(0, matcher.start()); + String after = input.substring(matcher.end()); + input = before + hexColor + after; + matcher = PATTERN.matcher(input); + } + + return ChatColor.translateAlternateColorCodes(COLOR_PREFIX, input); + } + } + +} diff --git a/api/src/main/java/ru/abstractmenus/api/variables/Var.java b/api/src/main/java/ru/abstractmenus/api/variables/Var.java new file mode 100644 index 0000000..fc0bd99 --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/variables/Var.java @@ -0,0 +1,172 @@ +package ru.abstractmenus.api.variables; + +/** + * Immutable snapshot of a single AbstractMenus variable — a named string + * value with an optional expiry timestamp. + * + *

Variables come in two flavours, distinguished only by how they are stored + * through {@link VariableManager}: + * + *

    + *
  • Global — server-wide, shared across players; + * exposed to menus as {@code %var_%} and manipulated by the + * {@code /var} command.
  • + *
  • Personal — scoped to a single player (by + * username); exposed as {@code %varp_%} and manipulated by the + * {@code /varp} command and by the {@code varpSet} / {@code varpAdd} + * menu actions.
  • + *
+ * + *

The value is always kept as a {@code String}; the numeric accessors are + * pure parse helpers and do not cache the parsed result. Instances are + * immutable — to change a field, call {@link #toBuilder()}, mutate the + * returned {@link VarBuilder}, and pass the rebuilt {@code Var} back to + * {@link VariableManager#saveGlobal(Var)} / + * {@link VariableManager#savePersonal(String, Var)}. + * + *

Example — increment a personal counter

+ * + *
{@code
+ * VariableManager vm = AbstractMenusApi.get().variables();
+ *
+ * Var counter = vm.getPersonal(player.getName(), "kills");
+ * long next = (counter == null) ? 1L : counter.longValue() + 1L;
+ *
+ * Var updated = (counter != null ? counter.toBuilder() : vm.createBuilder())
+ *         .name("kills")
+ *         .value(Long.toString(next))
+ *         .build();
+ *
+ * vm.savePersonal(player.getName(), updated);
+ * }
+ * + *

Menu usage

+ * + * Placeholders resolve the latest cached value on every lookup: + * + *
{@code
+ * items {
+ *   coin {
+ *     material: GOLD_NUGGET
+ *     name: "&eCoins: %varp_coins%"
+ *     actions.click: [
+ *       { type: varpAdd, name: coins, value: 1 }
+ *     ]
+ *   }
+ * }
+ * }
+ * + *

Threading

+ * + * Instances are safe to read from any thread — every field is final and + * the expiry check is a plain {@code System.currentTimeMillis()} comparison. + * Writes go through {@link VariableManager}; see that interface for the + * threading contract around persistence. + * + * @see VariableManager + * @see VarBuilder + */ +public interface Var { + + /** + * Variable name as registered. Names are case-insensitive at the storage + * layer but the returned string preserves the casing used when the + * variable was built. + * + * @return the variable name; never {@code null} + */ + String name(); + + /** + * Raw string value. Numeric variables are still returned as strings — + * use {@link #intValue()}, {@link #longValue()}, {@link #floatValue()} or + * {@link #doubleValue()} to parse on demand. + * + * @return the raw value; never {@code null} + */ + String value(); + + /** + * Absolute expiry timestamp in milliseconds since the Unix epoch, directly + * comparable with {@link System#currentTimeMillis()}. A return of + * {@code 0} signals "no expiry" — see {@link #hasExpiry()}. + * + * @return the expiry timestamp, or {@code 0} if the variable never expires + */ + long expiry(); + + /** + * Whether this variable was built with an expiry timestamp. + * + * @return {@code true} if {@link #expiry()} is strictly positive, + * {@code false} otherwise + */ + boolean hasExpiry(); + + /** + * Whether this variable has already expired relative to the current wall + * clock. Expired variables are evicted from the manager's cache by a + * periodic sweep (once per second) and are not written back to disk. + * + * @return {@code true} if {@link #hasExpiry()} is {@code true} and the + * expiry timestamp is in the past or present; {@code false} + * otherwise + */ + boolean isExpired(); + + /** + * Parse the raw value as a boolean. The raw value is considered + * {@code true} if, after lower-casing, it equals {@code "true"} or + * {@code "1"}; any other value — including {@code null}-like strings + * — is {@code false}. + * + * @return the parsed boolean value + */ + boolean boolValue(); + + /** + * Parse the raw value as a signed 32-bit integer. + * + * @return the parsed integer + * @throws NumberFormatException if the raw value is not a valid + * {@code int} literal + */ + int intValue() throws NumberFormatException; + + /** + * Parse the raw value as a signed 64-bit integer. + * + * @return the parsed long + * @throws NumberFormatException if the raw value is not a valid + * {@code long} literal + */ + long longValue() throws NumberFormatException; + + /** + * Parse the raw value as a single-precision float. + * + * @return the parsed float + * @throws NumberFormatException if the raw value is not a valid + * {@code float} literal + */ + float floatValue() throws NumberFormatException; + + /** + * Parse the raw value as a double-precision float. + * + * @return the parsed double + * @throws NumberFormatException if the raw value is not a valid + * {@code double} literal + */ + double doubleValue() throws NumberFormatException; + + /** + * Return a fresh {@link VarBuilder} pre-populated with this variable's + * fields. Modifying the builder does not affect this instance. + * + * @return a new builder seeded with {@link #name()}, {@link #value()} and + * {@link #expiry()} + */ + VarBuilder toBuilder(); + +} diff --git a/api/src/main/java/ru/abstractmenus/api/variables/VarBuilder.java b/api/src/main/java/ru/abstractmenus/api/variables/VarBuilder.java new file mode 100644 index 0000000..f789b96 --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/variables/VarBuilder.java @@ -0,0 +1,114 @@ +package ru.abstractmenus.api.variables; + +/** + * Fluent builder for {@link Var} instances. + * + *

Obtain an instance one of two ways, depending on whether you are creating + * a fresh variable or mutating an existing one: + * + *

    + *
  • {@link VariableManager#createBuilder()} — an empty builder for a + * brand-new variable.
  • + *
  • {@link Var#toBuilder()} — a builder pre-populated with the + * fields of an existing variable, as {@link Var} is immutable.
  • + *
+ * + *

{@link #name(String)} and {@link #value(String)} are required — + * {@link #build()} throws {@link NullPointerException} otherwise. Expiry is + * optional and defaults to {@code 0} (never expires). + * + *

Example — a 10-minute cooldown variable

+ * + *
{@code
+ * VariableManager vm = AbstractMenusApi.get().variables();
+ *
+ * Var cooldown = vm.createBuilder()
+ *         .name("ability_cooldown")
+ *         .value("active")
+ *         .expiry(System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(10))
+ *         .build();
+ *
+ * vm.savePersonal(player.getName(), cooldown);
+ * }
+ * + *

Threading

+ * + * Builders are not thread-safe — construct and complete + * them on a single thread. The {@link Var} produced by {@link #build()} is + * fully immutable and safe to share. + * + * @see Var + * @see VariableManager + */ +public interface VarBuilder { + + /** + * Current staged name. + * + * @return the name, or {@code null} if {@link #name(String)} has not yet + * been called + */ + String name(); + + /** + * Current staged value. + * + * @return the value, or {@code null} if {@link #value(String)} has not yet + * been called + */ + String value(); + + /** + * Current staged expiry timestamp. + * + * @return the expiry in epoch millis, or {@code 0} if no expiry has been + * set + */ + long expiry(); + + /** + * Set the variable name. Names may contain Latin letters, digits and the + * underscore character ({@code _}); validation happens at the manager + * layer when the variable is saved. + * + * @param name the variable name; must be non-{@code null} by + * {@link #build()} time + * @return this builder, for chaining + */ + VarBuilder name(String name); + + /** + * Set the raw string value. Numeric values should be stringified via + * {@code String.valueOf(…)} or {@link Long#toString(long)} — + * the storage layer does not accept typed numbers. + * + * @param value the raw value; must be non-{@code null} by {@link #build()} + * time + * @return this builder, for chaining + */ + VarBuilder value(String value); + + /** + * Set the absolute expiry timestamp in milliseconds since the Unix epoch. + * Use {@code System.currentTimeMillis() + lifetimeMillis} to express a + * relative lifetime; pass {@code 0} to disable expiry. + * + * @param expiry the expiry timestamp, or {@code 0} for no expiry + * @return this builder, for chaining + */ + VarBuilder expiry(long expiry); + + /** + * Materialise the staged fields into an immutable {@link Var}. + * + *

As a convenience, values that parse as floating-point numbers with a + * zero fractional part are normalised to their integer form (e.g. + * {@code "5.0"} becomes {@code "5"}). + * + * @return a new {@link Var}; never {@code null} + * @throws NullPointerException if {@link #name(String)} or + * {@link #value(String)} was not called + */ + Var build(); + +} diff --git a/api/src/main/java/ru/abstractmenus/api/variables/VariableManager.java b/api/src/main/java/ru/abstractmenus/api/variables/VariableManager.java new file mode 100644 index 0000000..205278b --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/variables/VariableManager.java @@ -0,0 +1,189 @@ +package ru.abstractmenus.api.variables; + +/** + * CRUD gateway for AbstractMenus variables — the runtime behind + * {@code %var_%} / {@code %varp_%} placeholders and the {@code /var} / + * {@code /varp} commands. + * + *

The manager maintains an in-memory cache keyed by a compound + * {@code scope:name} identifier: + * + *

    + *
  • Global variables live under a synthetic scope and are + * visible to every player.
  • + *
  • Personal variables live under the owner's username; + * two players may hold variables with the same {@code name} without + * collision.
  • + *
+ * + *

Variables are persisted to a SQLite database (per-server) and, when + * cross-server sync is enabled in {@code config.conf}, broadcast over BungeeCord + * to keep sibling servers in lock-step. Expired variables are swept from the + * cache once per second by a background task. + * + *

Obtaining the manager

+ * + *
{@code
+ * VariableManager vm = AbstractMenusApi.get().variables();
+ * }
+ * + *

Example — daily-reward gate

+ * + *
{@code
+ * Var claimed = vm.getPersonal(player.getName(), "dailyReward");
+ * if (claimed == null || claimed.isExpired()) {
+ *     economy.giveBalance(player, 500);
+ *
+ *     Var marker = vm.createBuilder()
+ *             .name("dailyReward")
+ *             .value("claimed")
+ *             .expiry(System.currentTimeMillis() + TimeUnit.HOURS.toMillis(24))
+ *             .build();
+ *
+ *     vm.savePersonal(player.getName(), marker);
+ * }
+ * }
+ * + *

Menu usage

+ * + * The same state drives rules and placeholder-backed item text: + * + *
{@code
+ * items {
+ *   daily {
+ *     material: CHEST
+ *     name: "&6Daily reward"
+ *     rules: [
+ *       { type: notVarpEquals, name: dailyReward, value: claimed }
+ *     ]
+ *     actions.click: [
+ *       { type: giveMoney,  amount: 500 }
+ *       { type: varpSet,    name: dailyReward, value: claimed, expiry: 86400000 }
+ *     ]
+ *   }
+ * }
+ * }
+ * + *

Threading

+ * + * Reads ({@link #getGlobal}, {@link #getPersonal}) hit the in-memory cache and + * are safe from any thread. Writes ({@link #saveGlobal saveGlobal}, + * {@link #savePersonal savePersonal}, {@link #deleteGlobal deleteGlobal}, + * {@link #deletePersonal deletePersonal}) update the cache synchronously but + * issue a SQLite write and, if enabled, a BungeeCord forward as part of the + * call — both may block. Prefer calling these from an async scheduler + * task when bulk-updating variables. + * + * @see Var + * @see VarBuilder + */ +public interface VariableManager { + + /** + * Look up a global variable by name. + * + * @param name the variable name; compared case-insensitively + * @return the cached {@link Var}, or {@code null} if no variable is + * registered under that name (or it has expired and been swept) + */ + Var getGlobal(String name); + + /** + * Look up a personal variable owned by {@code username}. + * + * @param username the owning player's name; compared case-insensitively + * @param name the variable name; compared case-insensitively + * @return the cached {@link Var}, or {@code null} if the player has no + * such variable (or it has expired and been swept) + */ + Var getPersonal(String username, String name); + + /** + * Persist a global variable. + * + *

If a variable with the same name already exists in the cache and + * {@code replace} is {@code false}, the call is a no-op. Expired incoming + * variables are silently dropped. + * + * @param var the variable to persist; never {@code null} + * @param replace whether to overwrite an existing entry with the same name + * @apiNote Triggers a SQLite write on the calling thread and, when + * cross-server sync is enabled, a {@code SyncVar} plugin message + * to BungeeCord. Not suitable for tight main-thread loops. + */ + void saveGlobal(Var var, boolean replace); + + /** + * Convenience wrapper that always overwrites an existing entry. + * + * @param var the variable to persist; never {@code null} + * @see #saveGlobal(Var, boolean) + */ + default void saveGlobal(Var var) { + saveGlobal(var, true); + } + + /** + * Persist a personal variable owned by {@code username}. + * + *

If a variable with the same name already exists for the player and + * {@code replace} is {@code false}, the call is a no-op. Expired incoming + * variables are silently dropped. + * + * @param username the owning player's name; never {@code null} + * @param var the variable to persist; never {@code null} + * @param replace whether to overwrite an existing entry with the same + * name + * @apiNote Triggers a SQLite write on the calling thread and, when + * cross-server sync is enabled, a {@code SyncVar} plugin message + * to BungeeCord. Not suitable for tight main-thread loops. + */ + void savePersonal(String username, Var var, boolean replace); + + /** + * Convenience wrapper that always overwrites an existing entry. + * + * @param username the owning player's name; never {@code null} + * @param var the variable to persist; never {@code null} + * @see #savePersonal(String, Var, boolean) + */ + default void savePersonal(String username, Var var) { + savePersonal(username, var, true); + } + + /** + * Delete a global variable. + * + *

Removes the entry from the cache, deletes the row from SQLite and, if + * cross-server sync is enabled, broadcasts the deletion to sibling + * servers. Unknown names are silently ignored. + * + * @param name the variable name; compared case-insensitively + * @apiNote Performs IO on the calling thread — consider scheduling + * off the main thread when deleting many variables in a row. + */ + void deleteGlobal(String name); + + /** + * Delete a personal variable owned by {@code username}. + * + *

Removes the entry from the cache, deletes the row from SQLite and, if + * cross-server sync is enabled, broadcasts the deletion to sibling + * servers. Unknown combinations are silently ignored. + * + * @param username the owning player's name; compared case-insensitively + * @param name the variable name; compared case-insensitively + * @apiNote Performs IO on the calling thread — consider scheduling + * off the main thread when deleting many variables in a row. + */ + void deletePersonal(String username, String name); + + /** + * Create an empty {@link VarBuilder} for a brand-new variable. To derive + * one from an existing variable instead, call {@link Var#toBuilder()}. + * + * @return a fresh builder; never {@code null} + */ + VarBuilder createBuilder(); + +} diff --git a/build.gradle b/build.gradle index ef7e150..997e5f5 100644 --- a/build.gradle +++ b/build.gradle @@ -1,154 +1,96 @@ -import proguard.gradle.ProGuardTask - -buildscript { - repositories { - mavenCentral() - mavenLocal() - } - dependencies { - classpath 'com.guardsquare:proguard-gradle:7.9.1' - } -} +// Common configuration applied to every subproject. +// Subproject-specific plugins (java-library, shadow, paperweight) stay in subprojects. plugins { - id 'java' - id 'com.gradleup.shadow' version '9.4.1' - id("io.papermc.paperweight.userdev") version "2.0.0-beta.21" + // Root needs the base lifecycle ('build', 'assemble', 'clean') so we + // can hook the dist aggregator below into it. + id 'base' } -java { - sourceCompatibility = "21" - targetCompatibility = "21" - toolchain { - languageVersion.set(JavaLanguageVersion.of(21)) - } -} - - -group 'ru.abstractmenus' -version '1.18.0' +allprojects { + group = 'ru.abstractmenus' + version = '2.0.0-alpha' -repositories { - mavenLocal() - mavenCentral() - maven { url 'https://repo.papermc.io/repository/maven-public/' } - maven { url 'https://jitpack.io' } - maven { url 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' } - maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } - maven { url 'https://maven.enginehub.org/repo/' } - maven { url 'https://repo.extendedclip.com/content/repositories/placeholderapi/' } - maven { url 'https://mvn.lumine.io/repository/maven-public/' } - maven { url 'https://repo.codemc.org/repository/maven-public/' - metadataSources { - artifact() - } - } - maven { - url 'https://repo.codemc.org/repository/maven-snapshots/' - metadataSources { - artifact() - } - } - maven { - url 'https://repo.codemc.org/repository/maven-releases/' - metadataSources { - artifact() - } + repositories { + mavenLocal() + mavenCentral() + maven { url 'https://repo.papermc.io/repository/maven-public/' } + maven { url 'https://jitpack.io' } } } -dependencies { - compileOnly 'org.projectlombok:lombok:1.18.44' - annotationProcessor 'org.projectlombok:lombok:1.18.44' - - paperweight.paperDevBundle("1.21.11-R0.1-SNAPSHOT") - implementation 'com.github.AbstractMenus:api:995cd8c9a9' - implementation 'com.fathzer:javaluator:3.0.6' - implementation 'net.kyori:adventure-platform-bukkit:4.4.1' - implementation 'net.kyori:adventure-text-minimessage:4.26.1' - implementation "com.github.technicallycoded:FoliaLib:0.4.4" - -// shadow files('libs/spigot-1.14.4.jar') - shadow files('libs/MMOItems-6.5.4.jar') - shadow files('libs/citizens-2.0.jar') +subprojects { + apply plugin: 'java' - shadow ('de.tr7zw:item-nbt-api-plugin:2.15.7') { - exclude group: 'org.bstats', module: 'bstats-bukkit' - } - shadow ('com.arcaniax:HeadDatabase-API:1.3.2'){ - exclude group: 'org.bstats', module: 'bstats-bukkit' - } - shadow ('com.github.MilkBowl:VaultAPI:1.7.1'){ - exclude group: 'org.bstats', module: 'bstats-bukkit' - exclude group: 'org.bukkit' - } - shadow ('net.luckperms:api:5.5'){ - exclude group: 'org.bstats', module: 'bstats-bukkit' - } - shadow ('com.sk89q.worldguard:worldguard-bukkit:7.0.0'){ - exclude group: 'org.bstats', module: 'bstats-bukkit' - exclude group: 'org.bukkit' - exclude group: 'io.papermc.paper' - } - - compileOnly 'me.clip:placeholderapi:2.12.2' - - shadow ('io.lumine:MythicLib:1.0.12-SNAPSHOT'){ - exclude group: 'org.bstats', module: 'bstats-bukkit' - } - shadow ('com.github.LoneDev6:api-itemsadder:3.6.1'){ - exclude group: 'org.bstats', module: 'bstats-bukkit' - } - shadow ('net.skinsrestorer:skinsrestorer-api:15.10.0'){ - exclude group: 'org.bstats', module: 'bstats-bukkit' - } - - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.14.3' - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.14.3' - testRuntimeOnly 'org.junit.platform:junit-platform-launcher' - - // MockBukkit — in-memory Paper API mock for integration tests - testImplementation 'org.mockbukkit.mockbukkit:mockbukkit-v1.21:4.108.0' - - // Mockito — isolated behavior tests against Bukkit API surface - testImplementation 'org.mockito:mockito-core:5.15.2' - testImplementation 'org.mockito:mockito-junit-jupiter:5.15.2' - - testImplementation 'org.openjdk.jmh:jmh-core:1.37' - testAnnotationProcessor 'org.openjdk.jmh:jmh-generator-annprocess:1.37' - - testCompileOnly 'org.projectlombok:lombok:1.18.44' - testAnnotationProcessor 'org.projectlombok:lombok:1.18.44' -} - -processResources { - filesMatching("plugin.yml") { - expand([version: project.version]) + java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } } -} -shadowJar { - relocate "com.tcoded.folialib", "ru.abstractmenus.lib.folialib" - dependencies { - exclude dependency('org.inventivetalent.spiget-update:bukkit') + test { + useJUnitPlatform() } - - archiveFileName.set("${project.name}-${project.version}.jar") } -test { - useJUnitPlatform() - exclude 'ru/abstractmenus/bench/**' +// ----------------------------------------------------------------------- +// dist - aggregate every shipping artifact into /build/dist/ +// +// Mirrors the user-facing files in one place so a server admin (or CI) +// can grab everything with a single cp / scp: +// +// build/dist/ +// AbstractMenus-.jar (plugin, fat shadow) +// AbstractMenus-api-.jar (api binary, Maven coord +// ru.abstractmenus:api:) +// AbstractMenus-api--sources.jar +// AbstractMenus-api--javadoc.jar +// +// The per-module build/libs/ directories are unchanged - existing CI +// paths and Gradle's incremental cache keep working. +// +// ./gradlew build automatically runs dist as a finalizer, so a normal +// build leaves everything in build/dist/. Run ./gradlew dist alone if +// you only need to refresh the aggregate (skips tests). +// ----------------------------------------------------------------------- +tasks.register('dist', Copy) { + description = 'Aggregates shipping artifacts into build/dist/.' + group = 'distribution' + + def pluginShadow = project(':plugin').tasks.named('shadowJar') + def apiJar = project(':api').tasks.named('jar') + def apiSources = project(':api').tasks.named('sourcesJar') + def apiJavadoc = project(':api').tasks.named('javadocJar') + + dependsOn pluginShadow, apiJar, apiSources, apiJavadoc + + from(pluginShadow.flatMap { it.archiveFile }) + from(apiJar.flatMap { it.archiveFile }) + from(apiSources.flatMap { it.archiveFile }) + from(apiJavadoc.flatMap { it.archiveFile }) + + into layout.buildDirectory.dir('dist') } -tasks.register('jmh', JavaExec) { - description = 'Run JMH benchmarks' - group = 'benchmark' - classpath = sourceSets.test.runtimeClasspath - mainClass = 'ru.abstractmenus.bench.RunBenchmarks' - - def benchFilter = project.findProperty('bench') ?: '' - if (benchFilter) { - args = ["ru.abstractmenus.bench.*${benchFilter}*"] +// Hook dist into every common dev-cycle entry point so the aggregate in +// build/dist/ is always fresh, regardless of which command the dev ran. +// +// ./gradlew shadowJar -> finalizes :dist (fast loop "fix -> server") +// ./gradlew assemble -> finalizes :dist (all artifacts, no tests) +// ./gradlew build -> finalizes :dist (artifacts + tests) +// ./gradlew dist -> dist itself +// +// finalizedBy is correct here (vs dependsOn) because we want dist to run +// AFTER the upstream task succeeds, not be pulled in as its dependency. +tasks.named('build').configure { finalizedBy('dist') } +tasks.named('assemble').configure { finalizedBy('dist') } + +// :plugin:shadowJar is the hot-path command during plugin development; +// hook it directly so the dev-loop jar lands in build/dist/ too. +project(':plugin').afterEvaluate { + project(':plugin').tasks.named('shadowJar').configure { + finalizedBy(':dist') } } diff --git a/jitpack.yml b/jitpack.yml new file mode 100644 index 0000000..8e1a95a --- /dev/null +++ b/jitpack.yml @@ -0,0 +1,8 @@ +jdk: + - openjdk21 + +before_install: + - sdk install java 21.0.3-tem < /dev/null || true + +install: + - ./gradlew :api:publishToMavenLocal -x test diff --git a/plugin/build.gradle b/plugin/build.gradle new file mode 100644 index 0000000..9092520 --- /dev/null +++ b/plugin/build.gradle @@ -0,0 +1,141 @@ +import proguard.gradle.ProGuardTask + +buildscript { + repositories { + mavenCentral() + mavenLocal() + } + dependencies { + classpath 'com.guardsquare:proguard-gradle:7.9.1' + } +} + +plugins { + id 'com.gradleup.shadow' version '9.4.1' + id 'io.papermc.paperweight.userdev' version '2.0.0-beta.21' +} + +// Plugin-specific repos not already in root +repositories { + maven { url 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' } + maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } + maven { url 'https://maven.enginehub.org/repo/' } + maven { url 'https://repo.extendedclip.com/content/repositories/placeholderapi/' } + maven { url 'https://mvn.lumine.io/repository/maven-public/' } + maven { + url 'https://repo.codemc.org/repository/maven-public/' + metadataSources { artifact() } + } + maven { + url 'https://repo.codemc.org/repository/maven-snapshots/' + metadataSources { artifact() } + } + maven { + url 'https://repo.codemc.org/repository/maven-releases/' + metadataSources { artifact() } + } +} + +dependencies { + compileOnly 'org.projectlombok:lombok:1.18.44' + annotationProcessor 'org.projectlombok:lombok:1.18.44' + + paperweight.paperDevBundle("1.21.11-R0.1-SNAPSHOT") + + implementation project(':api') + + implementation 'com.fathzer:javaluator:3.0.6' + implementation 'net.kyori:adventure-platform-bukkit:4.4.1' + implementation 'net.kyori:adventure-text-minimessage:4.26.1' + implementation "com.github.technicallycoded:FoliaLib:0.4.4" + + shadow files('libs/MMOItems-6.5.4.jar') + shadow files('libs/citizens-2.0.jar') + + shadow ('de.tr7zw:item-nbt-api-plugin:2.15.7') { + exclude group: 'org.bstats', module: 'bstats-bukkit' + } + shadow ('com.arcaniax:HeadDatabase-API:1.3.2') { + exclude group: 'org.bstats', module: 'bstats-bukkit' + } + shadow ('com.github.MilkBowl:VaultAPI:1.7.1') { + exclude group: 'org.bstats', module: 'bstats-bukkit' + exclude group: 'org.bukkit' + } + shadow ('net.luckperms:api:5.5') { + exclude group: 'org.bstats', module: 'bstats-bukkit' + } + shadow ('com.sk89q.worldguard:worldguard-bukkit:7.0.0') { + exclude group: 'org.bstats', module: 'bstats-bukkit' + exclude group: 'org.bukkit' + exclude group: 'io.papermc.paper' + } + + compileOnly 'me.clip:placeholderapi:2.12.2' + + shadow ('io.lumine:MythicLib:1.0.12-SNAPSHOT') { + exclude group: 'org.bstats', module: 'bstats-bukkit' + } + shadow ('com.github.LoneDev6:api-itemsadder:3.6.1') { + exclude group: 'org.bstats', module: 'bstats-bukkit' + } + shadow ('net.skinsrestorer:skinsrestorer-api:15.10.0') { + exclude group: 'org.bstats', module: 'bstats-bukkit' + } + + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.14.3' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.14.3' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + testImplementation 'org.mockbukkit.mockbukkit:mockbukkit-v1.21:4.108.0' + testImplementation 'org.mockito:mockito-core:5.15.2' + testImplementation 'org.mockito:mockito-junit-jupiter:5.15.2' + testImplementation 'org.openjdk.jmh:jmh-core:1.37' + testAnnotationProcessor 'org.openjdk.jmh:jmh-generator-annprocess:1.37' + testCompileOnly 'org.projectlombok:lombok:1.18.44' + testAnnotationProcessor 'org.projectlombok:lombok:1.18.44' +} + +// The :plugin module ships as a fat jar named AbstractMenus-.jar. +// Default Gradle naming would produce 'plugin-.jar' (using the +// subproject directory name), which is misleading on disk for ops. +base { + archivesName = 'AbstractMenus' +} + +processResources { + filesMatching("plugin.yml") { + expand([version: project.version]) + } +} + +// Thin jar lands as AbstractMenus--thin.jar so it cannot be confused +// with the user-facing fat jar produced by shadowJar. Devs-only artifact. +jar { + archiveClassifier.set('thin') +} + +shadowJar { + relocate "com.tcoded.folialib", "ru.abstractmenus.lib.folialib" + dependencies { + exclude dependency('org.inventivetalent.spiget-update:bukkit') + } + // Drop the default 'all' classifier so the fat jar is the canonical + // AbstractMenus-.jar that operators drop into plugins/. + archiveClassifier.set('') +} + +test { + exclude 'ru/abstractmenus/bench/**' +} + +tasks.register('jmh', JavaExec) { + description = 'Run JMH benchmarks' + group = 'benchmark' + classpath = sourceSets.test.runtimeClasspath + mainClass = 'ru.abstractmenus.bench.RunBenchmarks' + + def benchFilter = project.findProperty('bench') ?: '' + if (benchFilter) { + args = ["ru.abstractmenus.bench.*${benchFilter}*"] + } +} diff --git a/libs/MMOItems-6.5.4.jar b/plugin/libs/MMOItems-6.5.4.jar similarity index 100% rename from libs/MMOItems-6.5.4.jar rename to plugin/libs/MMOItems-6.5.4.jar diff --git a/libs/citizens-2.0.jar b/plugin/libs/citizens-2.0.jar similarity index 100% rename from libs/citizens-2.0.jar rename to plugin/libs/citizens-2.0.jar diff --git a/libs/paper-1.20.6-147.jar b/plugin/libs/paper-1.20.6-147.jar similarity index 100% rename from libs/paper-1.20.6-147.jar rename to plugin/libs/paper-1.20.6-147.jar diff --git a/proguard.txt b/plugin/proguard.txt similarity index 100% rename from proguard.txt rename to plugin/proguard.txt diff --git a/src/main/java/ru/abstractmenus/AbstractMenus.java b/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java similarity index 71% rename from src/main/java/ru/abstractmenus/AbstractMenus.java rename to plugin/src/main/java/ru/abstractmenus/AbstractMenus.java index 15a9367..9d70629 100644 --- a/src/main/java/ru/abstractmenus/AbstractMenus.java +++ b/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java @@ -3,16 +3,15 @@ import com.tcoded.folialib.FoliaLib; import lombok.Getter; import lombok.Setter; -import net.luckperms.api.LuckPerms; -import net.milkbowl.vault.economy.Economy; import org.bukkit.Bukkit; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.RegisteredServiceProvider; import org.bukkit.plugin.ServicePriority; import org.bukkit.plugin.java.JavaPlugin; import ru.abstractmenus.api.*; +import ru.abstractmenus.api.AbstractMenusApi; +import ru.abstractmenus.api.AbstractMenusApiImpl; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.text.Colors; import ru.abstractmenus.api.variables.VariableManager; @@ -21,25 +20,22 @@ import ru.abstractmenus.commands.Command; import ru.abstractmenus.commands.VarCommand; import ru.abstractmenus.commands.VarpCommand; +import ru.abstractmenus.commands.am.CommandAddons; import ru.abstractmenus.commands.am.CommandOpen; import ru.abstractmenus.commands.am.CommandPluginVersion; import ru.abstractmenus.commands.am.CommandReload; import ru.abstractmenus.commands.am.CommandServe; import ru.abstractmenus.commands.var.*; import ru.abstractmenus.commands.varp.*; -import ru.abstractmenus.data.actions.MenuActions; -import ru.abstractmenus.data.activators.Activators; -import ru.abstractmenus.data.catalogs.Catalogs; -import ru.abstractmenus.data.properties.ItemProps; -import ru.abstractmenus.data.rules.MenuRules; -import ru.abstractmenus.handlers.*; -import ru.abstractmenus.handlers.placeholder.PlaceholderCustomHandler; -import ru.abstractmenus.handlers.placeholder.PlaceholderDefaultHandler; +import ru.abstractmenus.addon.AddonManager; +import ru.abstractmenus.api.MenuExtension; +import ru.abstractmenus.core.CoreExtension; import ru.abstractmenus.hocon.api.ConfigurationLoader; import ru.abstractmenus.hocon.api.source.ConfigSources; import ru.abstractmenus.listeners.ChatListener; import ru.abstractmenus.listeners.InventoryListener; import ru.abstractmenus.listeners.PlayerListener; +import ru.abstractmenus.listeners.ServerLoadListener; import ru.abstractmenus.listeners.wg.WGHandlers; import ru.abstractmenus.nms.actionbar.ActionBar; import ru.abstractmenus.nms.title.Title; @@ -62,39 +58,39 @@ import java.util.Optional; @Getter -public final class AbstractMenus extends JavaPlugin implements AbstractMenusPlugin { +public final class AbstractMenus extends JavaPlugin { private static AbstractMenus instance; private CommandManager commandManager; private Metrics metrics; private FoliaLib foliaLib; + private AbstractMenusApi api; + private AddonManager addonManager; private MainConfig mainConfig; @Getter @Setter public boolean isProxyMode; - @Override public Plugin getPlugin() { return this; } - @Override + public AbstractMenusApi getApi() { return api; } + + public AddonManager getAddonManager() { return addonManager; } + public VariableManager getVariableManager() { return VariableManagerImpl.instance(); } - @Override public Optional

getOpenedMenu(Player player) { return Optional.ofNullable(MenuManager.instance().getOpenedMenu(player)); } @Override public void onLoad() { - AbstractMenusProvider.init(this); - getServer().getServicesManager() - .register(AbstractMenusPlugin.class, this, this, ServicePriority.Normal); } @Override @@ -137,17 +133,35 @@ public void onEnable() { new MenuManager(this, config); - registerProviders(); + // Publish the new Extension API to Bukkit's ServicesManager so + // plugin-as-addons (Path 1) and the dogfood CoreExtension (wired in + // the next phase) can look it up via AbstractMenusApi.get(). + this.api = new AbstractMenusApiImpl(this); + getServer().getServicesManager().register( + AbstractMenusApi.class, api, this, ServicePriority.Normal); + registerCommands(config); Serializers.init(this); - ItemProps.init(); - Activators.init(); - MenuActions.init(); - MenuRules.init(); - Catalogs.init(); - loadMenus(); + // Core extension — dogfood: the plugin registers its own types through + // the same SPI external addons will use. + MenuExtension core = new CoreExtension(); + core.onLoad(api); + core.onEnable(api); + + // External addons (plugins/AbstractMenus/addons/*.jar) load here so + // their registered types are available before menus parse. + this.addonManager = new AddonManager(this, api); + addonManager.loadAll(); + + // Menus parse via ServerLoadEvent (after every plugin's onEnable + // has completed) so Path 1 plugin-as-addons - which Bukkit only + // enables AFTER us due to depend: AbstractMenus - have a chance to + // register their providers before HOCON parse-time validation + // runs. Path 2 addons are already loaded above, so they work + // either way. + getServer().getPluginManager().registerEvents(new ServerLoadListener(), this); getServer().getPluginManager().registerEvents(new InventoryListener(), this); getServer().getPluginManager().registerEvents(new ProfileStorage(), this); @@ -165,7 +179,6 @@ public void onEnable() { } } - @Override public void loadMenus() { try { MenuManager.instance().loadMenus(); @@ -175,18 +188,18 @@ public void loadMenus() { } } - @Override public void openMenu(Player player, Menu menu) { MenuManager.instance().openMenu(player, menu); } - @Override public void openMenu(Activator activator, Object ctx, Player player, Menu menu) { MenuManager.instance().openMenu(activator, ctx, player, menu); } @Override public void onDisable() { + if (addonManager != null) addonManager.unloadAll(); + if (MenuManager.instance() != null) { MenuManager.instance().unloadAll(); } @@ -199,6 +212,7 @@ public void onDisable() { getServer().getMessenger().unregisterIncomingPluginChannel(this, "BungeeCord"); getServer().getMessenger().unregisterOutgoingPluginChannel(this, "BungeeCord"); + getServer().getServicesManager().unregisterAll(this); } private void registerCommands(MainConfig config) { @@ -206,7 +220,8 @@ private void registerCommands(MainConfig config) { .addSub("reload", new CommandReload()) .addSub("open", new CommandOpen(config)) .addSub("serve", new CommandServe()) - .addSub("version", new CommandPluginVersion()); + .addSub("version", new CommandPluginVersion()) + .addSub("addons", new CommandAddons()); Command var = new VarCommand("am.admin") .addSub("get", new VarGet()) @@ -226,61 +241,23 @@ private void registerCommands(MainConfig config) { .addSub("mul", new VarpMul()) .addSub("div", new VarpDiv()); - Objects.requireNonNull(getServer().getPluginCommand("am")).setExecutor(am); - Objects.requireNonNull(getServer().getPluginCommand("var")).setExecutor(var); - Objects.requireNonNull(getServer().getPluginCommand("varp")).setExecutor(varp); + var amCmd = Objects.requireNonNull(getServer().getPluginCommand("am")); + amCmd.setExecutor(am); + amCmd.setTabCompleter(am); + + var varCmd = Objects.requireNonNull(getServer().getPluginCommand("var")); + varCmd.setExecutor(var); + varCmd.setTabCompleter(var); + + var varpCmd = Objects.requireNonNull(getServer().getPluginCommand("varp")); + varpCmd.setExecutor(varp); + varpCmd.setTabCompleter(varp); } private void disablePlugin() { getServer().getPluginManager().disablePlugin(this); } - private void registerProviders() { - if (checkDependency("Vault")) { - RegisteredServiceProvider economyProvider = getServer().getServicesManager().getRegistration(Economy.class); - - if (economyProvider != null) { - Handlers.setEconomyHandler(new EconomyVaultHandler(economyProvider.getProvider())); - } else { - Logger.warning("Economy plugin doesn't installed"); - } - } else { - Logger.warning("Vault doesn't installed. Economy actions and rules won't work"); - } - - if (checkDependency("LuckPerms")) { - RegisteredServiceProvider provider = Bukkit.getServicesManager().getRegistration(LuckPerms.class); - - if (provider != null) { - Handlers.setPermissionsHandler(new LuckPermsHandler(provider.getProvider())); - Logger.info("Using LuckPerms"); - } else { - Logger.severe("Cannot find registered LuckPerms service"); - } - } else { - Handlers.setPermissionsHandler(new PermissionDefaultHandler(this)); - Logger.info("Using bundled temporary permissions manager"); - Logger.warning("LuckPerms doesn't installed. After reload all assigned permissions will be removed"); - } - - Handlers.setLevelHandler(new LevelDefaultHandler()); - - if (checkDependency("PlaceholderAPI")) { - Handlers.setPlaceholderHandler(new PlaceholderCustomHandler()); - Logger.info("Using PlaceholderAPI"); - } else { - Handlers.setPlaceholderHandler(new PlaceholderDefaultHandler()); - Logger.info("Using bundled placeholders"); - } - - Handlers.getPlaceholderHandler().registerAll(); - - if (checkDependency("SkinsRestorer")) { - Handlers.setSkinHandler(new SkinsRestorerHandler(isProxyMode, this)); - Logger.info("Using SkinsRestorer as skins provider"); - } - } - public static boolean checkDependency(String plugin) { return Bukkit.getServer().getPluginManager().isPluginEnabled(plugin); } diff --git a/src/main/java/ru/abstractmenus/Constants.java b/plugin/src/main/java/ru/abstractmenus/Constants.java similarity index 100% rename from src/main/java/ru/abstractmenus/Constants.java rename to plugin/src/main/java/ru/abstractmenus/Constants.java diff --git a/src/main/java/ru/abstractmenus/MainConfig.java b/plugin/src/main/java/ru/abstractmenus/MainConfig.java similarity index 81% rename from src/main/java/ru/abstractmenus/MainConfig.java rename to plugin/src/main/java/ru/abstractmenus/MainConfig.java index fd2c4e0..d38ba1c 100644 --- a/src/main/java/ru/abstractmenus/MainConfig.java +++ b/plugin/src/main/java/ru/abstractmenus/MainConfig.java @@ -6,6 +6,9 @@ import java.nio.file.Path; import java.nio.file.Paths; +import java.util.HashMap; +import java.util.List; +import java.util.Map; @Getter public final class MainConfig { @@ -26,6 +29,8 @@ public final class MainConfig { private Path menusFolder; private Path dbFolder; + private final Map providerDefaults = new HashMap<>(); + private long clickDebounceDefaultMs; private long clickDebounceShiftMs; @@ -59,7 +64,16 @@ public void load(Plugin plugin, ConfigNode node) { dbFolder = plugin.getDataFolder().toPath(); } + for (String kind : List.of("economy", "permissions", "levels", "placeholders", "skins")) { + String val = node.node("providers").node(kind).getString("auto"); + providerDefaults.put(kind, val); + } + clickDebounceDefaultMs = node.node("clickDebounce", "default").getLong(80L); clickDebounceShiftMs = node.node("clickDebounce", "shift").getLong(250L); } + + public String providerDefault(String kind) { + return providerDefaults.getOrDefault(kind, "auto"); + } } diff --git a/src/main/java/ru/abstractmenus/Metrics.java b/plugin/src/main/java/ru/abstractmenus/Metrics.java similarity index 100% rename from src/main/java/ru/abstractmenus/Metrics.java rename to plugin/src/main/java/ru/abstractmenus/Metrics.java diff --git a/plugin/src/main/java/ru/abstractmenus/addon/AddonClassLoader.java b/plugin/src/main/java/ru/abstractmenus/addon/AddonClassLoader.java new file mode 100644 index 0000000..df9a9b9 --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/addon/AddonClassLoader.java @@ -0,0 +1,74 @@ +package ru.abstractmenus.addon; + +import java.net.URL; +import java.net.URLClassLoader; + +/** + * Isolated classloader for an AM-loaded addon. + * + *

Parent-first for {@code ru.abstractmenus.api.*}, + * {@code org.bukkit.*}, Paper, Adventure, and the JDK — so the addon and + * the plugin share a single Class object for these. Otherwise + * {@code addon instanceof MenuExtension} would fail across classloader + * boundaries and Bukkit would refuse to accept the addon's event types. + * + *

Child-first for everything else — the addon can ship + * its own shaded copies of Gson, Guava, HTTP clients, etc. without + * conflicting with what AbstractMenus (or other addons) shade. + */ +public final class AddonClassLoader extends URLClassLoader { + + static final String[] PARENT_FIRST_PREFIXES = { + "ru.abstractmenus.api.", + "org.bukkit.", + "io.papermc.", + "com.destroystokyo.paper.", + "net.kyori.adventure.", + "java.", + "javax.", + "jdk.", + "sun." + }; + + public AddonClassLoader(URL[] urls, ClassLoader parent) { + super(urls, parent); + } + + @Override + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + synchronized (getClassLoadingLock(name)) { + Class c = findLoadedClass(name); + if (c != null) { + if (resolve) resolveClass(c); + return c; + } + + // Parent-first packages — always delegate first. + for (String prefix : PARENT_FIRST_PREFIXES) { + if (name.startsWith(prefix)) { + try { + c = getParent().loadClass(name); + if (resolve) resolveClass(c); + return c; + } catch (ClassNotFoundException ignored) { + // fall through — parent didn't have it, try the jar + } + } + } + + // Child-first: try our own URLs before delegating. + try { + c = findClass(name); + if (resolve) resolveClass(c); + return c; + } catch (ClassNotFoundException ignored) { + // Fall back to parent. + } + + // Final fallback — parent (may throw ClassNotFoundException). + c = getParent().loadClass(name); + if (resolve) resolveClass(c); + return c; + } + } +} diff --git a/plugin/src/main/java/ru/abstractmenus/addon/AddonConf.java b/plugin/src/main/java/ru/abstractmenus/addon/AddonConf.java new file mode 100644 index 0000000..455a8e8 --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/addon/AddonConf.java @@ -0,0 +1,125 @@ +package ru.abstractmenus.addon; + +import ru.abstractmenus.hocon.api.ConfigNode; +import ru.abstractmenus.hocon.api.ConfigurationLoader; +import ru.abstractmenus.hocon.api.source.ConfigSources; + +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; +import java.util.List; + +/** + * Immutable metadata parsed from an addon's {@code addon.conf} file. Use + * {@link #parse(String)} to construct from HOCON text. + * + *

Three of the nine fields are required ({@code name}, {@code version}, + * {@code main}); the rest default to empty or absent. + */ +public record AddonConf( + String name, + String version, + String main, + List authors, + String description, + String targetApiVersion, + List addonDependencies, + List pluginDependencies, + List pluginSoftDependencies +) { + + /** + * Parse HOCON text into an AddonConf. + * + * @param hocon raw text content of an {@code addon.conf} file + * @return parsed conf + * @throws AddonConfParseException if required fields are missing, fields + * have the wrong type, or the HOCON itself is malformed + */ + public static AddonConf parse(String hocon) { + ConfigNode root; + try { + ByteArrayInputStream in = new ByteArrayInputStream( + hocon.getBytes(StandardCharsets.UTF_8)); + root = ConfigurationLoader.builder() + .source(ConfigSources.inputStream("addon.conf", in)) + .build() + .load(); + } catch (Exception e) { + throw new AddonConfParseException("Failed to parse addon.conf: " + e.getMessage(), e); + } + + String name = requireString(root, "name"); + String version = requireString(root, "version"); + String main = requireString(root, "main"); + + List authors = optionalStringList(root, "authors"); + String description = root.node("description").getString(""); + String targetApiVersion = optionalString(root, "targetApiVersion"); + List addonDependencies = optionalStringList(root, "addonDependencies"); + List pluginDependencies = optionalStringList(root, "pluginDependencies"); + List pluginSoftDependencies = optionalStringList(root, "pluginSoftDependencies"); + + return new AddonConf( + name, version, main, + authors, description, targetApiVersion, + addonDependencies, pluginDependencies, pluginSoftDependencies + ); + } + + // ------------------------------------------------------------------------- + // Helpers + // ------------------------------------------------------------------------- + + /** + * Returns the string value of a required field. + * + * @throws AddonConfParseException if the field is absent, null, or blank + */ + private static String requireString(ConfigNode root, String field) { + ConfigNode node = root.node(field); + if (node.isNull()) { + throw new AddonConfParseException( + "Required field '" + field + "' is missing or blank"); + } + String value = node.getString(null); + if (value == null || value.isBlank()) { + throw new AddonConfParseException( + "Required field '" + field + "' is missing or blank"); + } + return value; + } + + /** + * Returns an optional string value, or {@code null} if absent. + */ + private static String optionalString(ConfigNode root, String field) { + ConfigNode node = root.node(field); + if (node.isNull()) { + return null; + } + return node.getString(null); + } + + /** + * Returns an optional list of strings. Accepts either a HOCON list or a + * single string (auto-wrapped into a one-element list). Returns + * {@link List#of()} if the field is absent. + */ + private static List optionalStringList(ConfigNode root, String field) { + ConfigNode node = root.node(field); + if (node.isNull()) { + return List.of(); + } + // Single-string scalar: wrap in a one-element list. + if (node.isPrimitive()) { + String value = node.getString(null); + return value != null ? List.of(value) : List.of(); + } + try { + return List.copyOf(node.getList(String.class)); + } catch (Exception e) { + throw new AddonConfParseException( + "Field '" + field + "' must be a string or list of strings: " + e.getMessage(), e); + } + } +} diff --git a/plugin/src/main/java/ru/abstractmenus/addon/AddonConfParseException.java b/plugin/src/main/java/ru/abstractmenus/addon/AddonConfParseException.java new file mode 100644 index 0000000..5dc9091 --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/addon/AddonConfParseException.java @@ -0,0 +1,13 @@ +package ru.abstractmenus.addon; + +/** Unchecked exception for malformed or incomplete {@code addon.conf} files. */ +public class AddonConfParseException extends RuntimeException { + + public AddonConfParseException(String message) { + super(message); + } + + public AddonConfParseException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/plugin/src/main/java/ru/abstractmenus/addon/AddonDependencyCycleException.java b/plugin/src/main/java/ru/abstractmenus/addon/AddonDependencyCycleException.java new file mode 100644 index 0000000..d0506b0 --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/addon/AddonDependencyCycleException.java @@ -0,0 +1,6 @@ +package ru.abstractmenus.addon; + +/** Raised when {@code addonDependencies} form a cycle. */ +public class AddonDependencyCycleException extends AddonDependencyException { + public AddonDependencyCycleException(String message) { super(message); } +} diff --git a/plugin/src/main/java/ru/abstractmenus/addon/AddonDependencyException.java b/plugin/src/main/java/ru/abstractmenus/addon/AddonDependencyException.java new file mode 100644 index 0000000..ac899e4 --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/addon/AddonDependencyException.java @@ -0,0 +1,6 @@ +package ru.abstractmenus.addon; + +/** Raised when an addon declares a dependency that isn't satisfiable. */ +public class AddonDependencyException extends RuntimeException { + public AddonDependencyException(String message) { super(message); } +} diff --git a/plugin/src/main/java/ru/abstractmenus/addon/AddonDependencyGraph.java b/plugin/src/main/java/ru/abstractmenus/addon/AddonDependencyGraph.java new file mode 100644 index 0000000..e2ae328 --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/addon/AddonDependencyGraph.java @@ -0,0 +1,85 @@ +package ru.abstractmenus.addon; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Topological sort over an addon dependency graph. Pure function — no state. + * + *

DFS-based post-order traversal. Cycle detection uses the in-progress + * (temporary/grey) set to identify nodes revisited during an active DFS path. + */ +public final class AddonDependencyGraph { + + private AddonDependencyGraph() {} + + /** + * Sort addons into enabling order. + * + * @param dependencies map from addon name → list of names it depends on. + * Iteration order of the input map is preserved + * among independent addons (pass a + * {@link java.util.LinkedHashMap} for determinism). + * @return addon names in dependency-first order (deps before dependants) + * @throws AddonDependencyCycleException if a cycle is detected + * @throws AddonDependencyException if a declared dep refers to a + * name not present in the graph + */ + public static List topoSort(Map> dependencies) { + Set known = dependencies.keySet(); + + // Validation: every declared dep must exist in the graph. + for (Map.Entry> e : dependencies.entrySet()) { + for (String dep : e.getValue()) { + if (!known.contains(dep)) { + throw new AddonDependencyException( + "Addon '" + e.getKey() + "' depends on unknown addon '" + dep + "'"); + } + } + } + + Set permanent = new HashSet<>(); + Set temporary = new LinkedHashSet<>(); // order preserved for cycle message + List order = new ArrayList<>(); + + for (String node : dependencies.keySet()) { + if (!permanent.contains(node)) { + visit(node, dependencies, permanent, temporary, order); + } + } + + return order; + } + + private static void visit(String node, + Map> deps, + Set permanent, + Set temporary, + List order) { + if (permanent.contains(node)) return; + if (temporary.contains(node)) { + Deque cycle = new ArrayDeque<>(); + boolean found = false; + for (String n : temporary) { + if (n.equals(node)) found = true; + if (found) cycle.addLast(n); + } + cycle.addLast(node); + throw new AddonDependencyCycleException( + "Addon dependency cycle: " + String.join(" -> ", cycle)); + } + temporary.add(node); + for (String dep : deps.getOrDefault(node, List.of())) { + visit(dep, deps, permanent, temporary, order); + } + temporary.remove(node); + permanent.add(node); + order.add(node); + } +} diff --git a/plugin/src/main/java/ru/abstractmenus/addon/AddonManager.java b/plugin/src/main/java/ru/abstractmenus/addon/AddonManager.java new file mode 100644 index 0000000..8747ffe --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/addon/AddonManager.java @@ -0,0 +1,498 @@ +package ru.abstractmenus.addon; + +import ru.abstractmenus.AbstractMenus; +import ru.abstractmenus.api.AbstractMenusApi; +import ru.abstractmenus.api.Logger; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * Loads, enables, and manages AM-loaded addons (the lightweight jars in + * {@code plugins/AbstractMenus/addons/}). + * + *

Plugin-as-addons (Path 1) are not handled here — they boot through + * Bukkit's own plugin lifecycle and look up the API via + * {@link AbstractMenusApi#get()}. + */ +public final class AddonManager { + + private final AbstractMenus plugin; + private final Path addonsDir; + private final AbstractMenusApi api; + + /** name (lowercased) → LoadedAddon, insertion-ordered (matches enable order) */ + private final Map addons = new LinkedHashMap<>(); + + public AddonManager(AbstractMenus plugin, AbstractMenusApi api) { + this.plugin = plugin; + this.api = api; + this.addonsDir = plugin.getDataFolder().toPath().resolve("addons"); + } + + /** + * Test-only overload: inject addonsDir directly, skip Bukkit plugin-dep + * checks (since no {@code plugin.getServer()} is available in pure-unit + * tests). Any addon with a non-empty {@code pluginDependencies} will fail + * under this constructor. + */ + AddonManager(Path addonsDir, AbstractMenusApi api) { + this.plugin = null; + this.api = api; + this.addonsDir = addonsDir; + } + + /** + * Discover, parse, sort, and enable every addon in the addons directory. + * Safe to call once during plugin enable. If the directory doesn't exist, + * creates it and returns without enabling anything. + */ + public void loadAll() { + Map pending = discover(); + if (pending.isEmpty()) { + Logger.info("No AM-loaded addons found in " + addonsDir); + return; + } + + // Soft-filter: drop addons whose required Bukkit plugin deps are missing. + // In test mode (plugin == null), skip this check — tests must not declare + // pluginDependencies. + var byName = new LinkedHashMap(); + if (plugin != null) { + var pluginManager = plugin.getServer().getPluginManager(); + for (LoadedAddon la : pending.values()) { + AddonConf c = la.getConf(); + boolean missing = false; + for (String dep : c.pluginDependencies()) { + if (pluginManager.getPlugin(dep) == null) { + Logger.warning("Addon " + c.name() + + " requires plugin '" + dep + "' which is not installed — skipping"); + la.markFailed(new IllegalStateException("missing plugin dependency: " + dep)); + missing = true; + break; + } + } + if (missing) { + addons.put(c.name().toLowerCase(), la); // keep the failed entry visible in /am addons list + continue; + } + byName.put(c.name().toLowerCase(), la); + } + } else { + byName.putAll(pending); + } + + if (byName.isEmpty()) return; + + // Sort by addon-level dependencies. + Map> depGraph = new LinkedHashMap<>(); + for (var e : byName.entrySet()) { + List deps = e.getValue().getConf().addonDependencies().stream() + .map(String::toLowerCase).toList(); + depGraph.put(e.getKey(), deps); + } + List order; + try { + order = AddonDependencyGraph.topoSort(depGraph); + } catch (AddonDependencyException ex) { + Logger.severe("Addon dependency graph error: " + ex.getMessage()); + for (var la : byName.values()) { + la.markFailed(ex); + addons.put(la.getConf().name().toLowerCase(), la); + } + return; + } + + // Stage 1: onLoad for all — ordering-independent setup. + for (String k : order) { + LoadedAddon la = byName.get(k); + try { + la.setExtension(instantiate(la)); + la.getExtension().onLoad(api); + } catch (Throwable t) { + Logger.severe("Addon " + la.getConf().name() + " failed in onLoad: " + t); + t.printStackTrace(); + la.markFailed(t); + } + } + + // Stage 2: onEnable in dependency order. + for (String k : order) { + LoadedAddon la = byName.get(k); + if (la.getStatus() == AddonStatus.FAILED) { + addons.put(k, la); + continue; + } + try { + la.getExtension().onEnable(api); + la.markEnabled(); + Logger.info("Enabled addon: " + la.getConf().name() + + " v" + la.getConf().version() + + (la.getConf().targetApiVersion() == null + ? "" + : " (built against API " + la.getConf().targetApiVersion() + ")")); + } catch (Throwable t) { + Logger.severe("Addon " + la.getConf().name() + " failed in onEnable: " + t); + t.printStackTrace(); + la.markFailed(t); + rollbackRegistrations(la); + } + addons.put(k, la); + } + } + + /** + * Reflectively instantiate the addon's main class and verify it implements + * MenuExtension. + */ + private ru.abstractmenus.api.MenuExtension instantiate(LoadedAddon la) throws Exception { + Class main = la.getClassLoader().loadClass(la.getConf().main()); + if (!ru.abstractmenus.api.MenuExtension.class.isAssignableFrom(main)) { + throw new IllegalStateException("main class " + main.getName() + + " does not implement MenuExtension"); + } + return (ru.abstractmenus.api.MenuExtension) main.getDeclaredConstructor().newInstance(); + } + + /** Strip any type registrations the failed addon managed to make. */ + private void rollbackRegistrations(LoadedAddon la) { + if (la.getExtension() == null) return; + api.actions().unregisterAll(la.getExtension()); + api.rules().unregisterAll(la.getExtension()); + api.activators().unregisterAll(la.getExtension()); + api.itemProperties().unregisterAll(la.getExtension()); + api.catalogs().unregisterAll(la.getExtension()); + } + + /** + * Disable every loaded addon (in reverse enable order) and release + * classloader resources. Called from plugin onDisable. + */ + public void unloadAll() { + // Disable in reverse enable order. + var reversed = new java.util.ArrayList<>(addons.values()); + java.util.Collections.reverse(reversed); + for (LoadedAddon la : reversed) { + try { + if (la.getStatus() == AddonStatus.ENABLED && la.getExtension() != null) { + la.getExtension().onDisable(api); + } + rollbackRegistrations(la); + la.markDisabled(); + } catch (Throwable t) { + Logger.severe("Addon " + la.getConf().name() + " failed in onDisable: " + t); + t.printStackTrace(); + // Don't let one bad disable block the others. + } + try { + la.getClassLoader().close(); + } catch (Exception e) { + Logger.warning("Addon " + la.getConf().name() + " classloader close failed: " + e); + } + } + addons.clear(); + } + + /** + * Reload a single AM-loaded addon by name: disable → close classloader → + * re-parse the jar → enable. Returns the new {@link LoadedAddon}, or + * empty if no addon of that name is currently loaded. + * + * @param name addon name (case-insensitive) + * @return the freshly loaded addon, or empty if not found / no jar present + */ + public Optional reload(String name) { + String key = name.toLowerCase(); + LoadedAddon existing = addons.get(key); + if (existing == null) return Optional.empty(); + + // Disable + unhook current instance. + try { + if (existing.getStatus() == AddonStatus.ENABLED && existing.getExtension() != null) { + existing.getExtension().onDisable(api); + } + rollbackRegistrations(existing); + } catch (Throwable t) { + Logger.warning("Addon " + existing.getConf().name() + + " failed in onDisable during reload: " + t); + } + try { existing.getClassLoader().close(); } catch (Exception ignored) {} + addons.remove(key); + + // Re-discover: find the jar whose addon.conf name matches. + Path freshJar = findJarByName(name); + if (freshJar == null) { + Logger.warning("Addon " + name + " jar no longer present — not reloaded"); + return Optional.empty(); + } + + LoadedAddon fresh; + try { + fresh = readAddonJar(freshJar); + } catch (Exception e) { + Logger.severe("Addon " + name + " jar failed to re-parse: " + e.getMessage()); + return Optional.empty(); + } + + // Enable the single addon. We don't re-chain the full topological sort + // for a single-addon reload — assume its addonDependencies are already + // enabled (they were, before this reload). + try { + fresh.setExtension(instantiate(fresh)); + fresh.getExtension().onLoad(api); + fresh.getExtension().onEnable(api); + fresh.markEnabled(); + addons.put(fresh.getConf().name().toLowerCase(), fresh); + Logger.info("Reloaded addon: " + fresh.getConf().name() + " v" + fresh.getConf().version()); + } catch (Throwable t) { + Logger.severe("Addon " + name + " failed during reload: " + t); + t.printStackTrace(); + fresh.markFailed(t); + rollbackRegistrations(fresh); + addons.put(fresh.getConf().name().toLowerCase(), fresh); + } + + return Optional.of(fresh); + } + + /** Scan addonsDir again, return the first jar whose addon.conf.name matches. */ + private Path findJarByName(String name) { + if (!java.nio.file.Files.isDirectory(addonsDir)) return null; + try (var stream = java.nio.file.Files.newDirectoryStream(addonsDir, "*.jar")) { + for (Path jar : stream) { + try (var jf = new java.util.jar.JarFile(jar.toFile())) { + var entry = jf.getJarEntry("addon.conf"); + if (entry == null) continue; + String hocon = new String(jf.getInputStream(entry).readAllBytes(), + java.nio.charset.StandardCharsets.UTF_8); + AddonConf c = AddonConf.parse(hocon); + if (c.name().equalsIgnoreCase(name)) return jar; + } catch (Exception ignored) {} + } + } catch (Exception ignored) {} + return null; + } + + public Collection loaded() { + return Collections.unmodifiableCollection(addons.values()); + } + + public Optional get(String name) { + return Optional.ofNullable(addons.get(name.toLowerCase())); + } + + /** + * Scan {@link #addonsDir} for {@code *.jar} files. For each, extract + * {@code addon.conf}, parse it, and build a LoadedAddon (without enabling + * — status stays PENDING). + * + *

Jars that are missing addon.conf, have malformed addon.conf, or + * duplicate a name already seen are logged and skipped — not fatal. + * + * @return map of name (lowercased) → PENDING LoadedAddon, in discovery + * order (stable for the later topological sort) + */ + Map discover() { + Map pending = new LinkedHashMap<>(); + + if (!java.nio.file.Files.isDirectory(addonsDir)) { + try { + java.nio.file.Files.createDirectories(addonsDir); + } catch (java.io.IOException e) { + Logger.warning("Could not create addons directory " + addonsDir + ": " + e.getMessage()); + } + return pending; + } + + try (var stream = java.nio.file.Files.newDirectoryStream(addonsDir, "*.jar")) { + for (Path jar : stream) { + try { + LoadedAddon addon = readAddonJar(jar); + String key = addon.getConf().name().toLowerCase(); + if (pending.containsKey(key)) { + Logger.warning("Duplicate addon name '" + addon.getConf().name() + + "' — ignoring " + jar.getFileName()); + try { addon.getClassLoader().close(); } catch (Exception ignored) {} + continue; + } + pending.put(key, addon); + } catch (Exception e) { + Logger.warning("Failed to load addon " + jar.getFileName() + ": " + e.getMessage()); + } + } + } catch (java.io.IOException e) { + Logger.warning("Failed to scan addons directory: " + e.getMessage()); + } + + return pending; + } + + /** + * Load a single addon by its addon.conf {@code name} from the addons + * directory. Useful when the operator drops one new jar at runtime + * and runs {@code /am addons load } - no need to bounce the + * server to discover it. + * + *

Returns {@code Optional.empty()} if no jar with a matching + * {@code addon.conf name} is found, or if an addon with that name is + * already in the loaded map. Otherwise returns the {@link LoadedAddon} + * (which may be ENABLED or FAILED depending on what happened). + * + * @param name the addon-conf {@code name}, case-insensitive + */ + public Optional loadOne(String name) { + if (addons.containsKey(name.toLowerCase())) { + return Optional.empty(); + } + Path jar = findJarByName(name); + if (jar == null) return Optional.empty(); + + LoadedAddon la; + try { + la = readAddonJar(jar); + } catch (Exception e) { + Logger.severe("Addon " + name + " failed to parse: " + e.getMessage()); + return Optional.empty(); + } + enableSingle(la); + return Optional.of(la); + } + + /** + * Re-scan the addons directory and load every addon not already in + * the loaded map. Existing addons are left alone (their classloader + * is not rebuilt - use {@link #reload(String)} for that). Returns + * the list of addons that were attempted in this call (some may + * have ended up in FAILED state). + */ + public List rescan() { + List newlyLoaded = new ArrayList<>(); + Map discovered = discover(); + for (var entry : discovered.entrySet()) { + LoadedAddon la = entry.getValue(); + if (addons.containsKey(entry.getKey())) { + // Already loaded - drop the redundant classloader we just built. + try { la.getClassLoader().close(); } catch (Exception ignored) {} + continue; + } + enableSingle(la); + newlyLoaded.add(la); + } + return newlyLoaded; + } + + /** + * Return addon-conf {@code name}s found on disk under the addons + * directory but not yet loaded into memory. Used by tab completion + * for {@code /am addons load }. Cost is one jar open and one + * HOCON parse per .jar in the directory - acceptable at typical + * scale (1-20 addons), but be aware this is not free. + */ + public List availableNotLoaded() { + if (!java.nio.file.Files.isDirectory(addonsDir)) return List.of(); + List result = new ArrayList<>(); + try (var stream = java.nio.file.Files.newDirectoryStream(addonsDir, "*.jar")) { + for (Path jar : stream) { + try (var jf = new java.util.jar.JarFile(jar.toFile())) { + var entry = jf.getJarEntry("addon.conf"); + if (entry == null) continue; + String hocon = new String(jf.getInputStream(entry).readAllBytes(), + java.nio.charset.StandardCharsets.UTF_8); + AddonConf conf = AddonConf.parse(hocon); + if (!addons.containsKey(conf.name().toLowerCase())) { + result.add(conf.name()); + } + } catch (Exception ignored) { + // Malformed jar - skip silently, the operator already saw + // the warning at server-start discover() time. + } + } + } catch (Exception ignored) {} + return result; + } + + /** + * Verify Bukkit-side and addon-side dependencies, then run + * onLoad + onEnable. Installs the result into the loaded map + * (regardless of success or failure - failed addons stay visible + * in {@code /am addons list} so the operator can debug them). + */ + private void enableSingle(LoadedAddon la) { + String key = la.getConf().name().toLowerCase(); + + if (plugin != null) { + var pm = plugin.getServer().getPluginManager(); + for (String dep : la.getConf().pluginDependencies()) { + if (pm.getPlugin(dep) == null) { + String msg = "missing plugin dependency: " + dep; + Logger.warning("Addon " + la.getConf().name() + " " + msg); + la.markFailed(new IllegalStateException(msg)); + addons.put(key, la); + return; + } + } + } + + for (String dep : la.getConf().addonDependencies()) { + LoadedAddon depAddon = addons.get(dep.toLowerCase()); + if (depAddon == null || depAddon.getStatus() != AddonStatus.ENABLED) { + String msg = "missing or unhealthy addon dependency: " + dep; + Logger.warning("Addon " + la.getConf().name() + " " + msg); + la.markFailed(new IllegalStateException(msg)); + addons.put(key, la); + return; + } + } + + try { + la.setExtension(instantiate(la)); + la.getExtension().onLoad(api); + la.getExtension().onEnable(api); + la.markEnabled(); + Logger.info("Enabled addon: " + la.getConf().name() + + " v" + la.getConf().version() + + (la.getConf().targetApiVersion() == null ? "" + : " (built against API " + la.getConf().targetApiVersion() + ")")); + } catch (Throwable t) { + Logger.severe("Addon " + la.getConf().name() + " failed during enable: " + t); + t.printStackTrace(); + la.markFailed(t); + rollbackRegistrations(la); + } + addons.put(key, la); + } + + /** + * Read a single addon jar: extract {@code addon.conf}, parse it, build a + * classloader. Throws if addon.conf is missing or malformed. + */ + private LoadedAddon readAddonJar(Path jarPath) throws java.io.IOException { + String hocon; + try (var jar = new java.util.jar.JarFile(jarPath.toFile())) { + var entry = jar.getJarEntry("addon.conf"); + if (entry == null) { + throw new java.io.IOException("no addon.conf at jar root"); + } + try (var in = jar.getInputStream(entry)) { + hocon = new String(in.readAllBytes(), java.nio.charset.StandardCharsets.UTF_8); + } + } + + AddonConf conf = AddonConf.parse(hocon); + ClassLoader parent = (plugin != null) + ? plugin.getClass().getClassLoader() + : AddonManager.class.getClassLoader(); + AddonClassLoader cl = new AddonClassLoader( + new java.net.URL[]{jarPath.toUri().toURL()}, + parent); + + return new LoadedAddon(conf, cl); + } + +} diff --git a/plugin/src/main/java/ru/abstractmenus/addon/AddonStatus.java b/plugin/src/main/java/ru/abstractmenus/addon/AddonStatus.java new file mode 100644 index 0000000..b33add0 --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/addon/AddonStatus.java @@ -0,0 +1,13 @@ +package ru.abstractmenus.addon; + +/** Lifecycle state of an addon as tracked by {@code AddonManager}. */ +public enum AddonStatus { + /** Addon discovered and parsed; not yet enabled. */ + PENDING, + /** Enabled — {@code onEnable} completed without throwing. */ + ENABLED, + /** Cleanly disabled — {@code onDisable} ran and registry entries were cleared. */ + DISABLED, + /** Failed to load or enable — see {@link LoadedAddon#getError()} for the cause. */ + FAILED +} diff --git a/plugin/src/main/java/ru/abstractmenus/addon/LoadedAddon.java b/plugin/src/main/java/ru/abstractmenus/addon/LoadedAddon.java new file mode 100644 index 0000000..642acbc --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/addon/LoadedAddon.java @@ -0,0 +1,42 @@ +package ru.abstractmenus.addon; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import ru.abstractmenus.api.MenuExtension; + +/** + * Mutable container representing an AM-loaded addon. Holds the parsed + * {@link AddonConf}, the live {@link MenuExtension} instance, its + * {@link AddonClassLoader}, and lifecycle status. + * + *

Instances are created by {@code AddonManager} during discovery and + * transition through enable → disable / failed states. + */ +@RequiredArgsConstructor +@Getter +public final class LoadedAddon { + + + private final AddonConf conf; + private final AddonClassLoader classLoader; + @Setter + private MenuExtension extension; // null until onLoad completes + private AddonStatus status = AddonStatus.PENDING; + private Throwable error; // non-null iff status == FAILED + + public void markEnabled() { + this.status = AddonStatus.ENABLED; + this.error = null; + } + + public void markDisabled() { + this.status = AddonStatus.DISABLED; + this.error = null; + } + + public void markFailed(Throwable t) { + this.status = AddonStatus.FAILED; + this.error = t; + } +} diff --git a/plugin/src/main/java/ru/abstractmenus/api/AbstractMenusApiImpl.java b/plugin/src/main/java/ru/abstractmenus/api/AbstractMenusApiImpl.java new file mode 100644 index 0000000..922ab3a --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/api/AbstractMenusApiImpl.java @@ -0,0 +1,117 @@ +package ru.abstractmenus.api; + +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import ru.abstractmenus.AbstractMenus; +import ru.abstractmenus.api.inventory.ItemProperty; +import ru.abstractmenus.api.inventory.Menu; +import ru.abstractmenus.api.variables.VariableManager; +import ru.abstractmenus.hocon.api.serialize.NodeSerializers; + +import java.util.Optional; + +/** + * Main implementation of {@link AbstractMenusApi}. Instantiated once during + * plugin enable; published to Bukkit's ServicesManager so + * {@link AbstractMenusApi#get()} resolves it. + */ +public final class AbstractMenusApiImpl implements AbstractMenusApi { + + private final AbstractMenus plugin; + private final NodeSerializers serializers = NodeSerializers.defaults(); + + private final TypeRegistry actions; + private final TypeRegistry rules; + private final TypeRegistry activators; + private final TypeRegistry itemProperties; + private final TypeRegistry> catalogs; + private final ProviderRegistry providers; + + public AbstractMenusApiImpl(AbstractMenus plugin) { + this.plugin = plugin; + this.actions = new TypeRegistryImpl<>(serializers); + this.rules = new TypeRegistryImpl<>(serializers); + this.activators = new TypeRegistryImpl<>(serializers); + this.itemProperties = new TypeRegistryImpl<>(serializers); + this.catalogs = new TypeRegistryImpl<>(serializers); + ProviderRegistryImpl providerImpl = new ProviderRegistryImpl(); + providerImpl.setConfigDefaults(kind -> plugin.getMainConfig().providerDefault(kind)); + this.providers = providerImpl; + } + + @Override + public TypeRegistry actions() { + return actions; + } + + @Override + public TypeRegistry rules() { + return rules; + } + + @Override + public TypeRegistry activators() { + return activators; + } + + @Override + public TypeRegistry itemProperties() { + return itemProperties; + } + + @Override + public TypeRegistry> catalogs() { + return catalogs; + } + + @Override + public ProviderRegistry providers() { + return providers; + } + + @Override + public NodeSerializers serializers() { + return serializers; + } + + @Override + public VariableManager variables() { + return plugin.getVariableManager(); + } + + @Override + public Plugin getPlugin() { + return plugin; + } + + @Override + public void loadMenus() { + plugin.loadMenus(); + } + + @Override + public void openMenu(Activator activator, Object ctx, Player player, Menu menu) { + plugin.openMenu(activator, ctx, player, menu); + } + + @Override + public void openMenu(Player player, Menu menu) { + plugin.openMenu(player, menu); + } + + @Override + public Optional

getOpenedMenu(Player player) { + return plugin.getOpenedMenu(player); + } + + @Override + public String apiVersion() { + // Paper 1.21+ exposes getPluginMeta().getVersion(); fall back to + // getDescription().getVersion() if the new API isn't present. + try { + return plugin.getPluginMeta().getVersion(); + } catch (NoSuchMethodError | AbstractMethodError ignored) { + return plugin.getDescription().getVersion(); + } + } +} diff --git a/plugin/src/main/java/ru/abstractmenus/api/ProviderRegistryImpl.java b/plugin/src/main/java/ru/abstractmenus/api/ProviderRegistryImpl.java new file mode 100644 index 0000000..50ae7b4 --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/api/ProviderRegistryImpl.java @@ -0,0 +1,155 @@ +package ru.abstractmenus.api; + +import ru.abstractmenus.api.handler.EconomyHandler; +import ru.abstractmenus.api.handler.LevelHandler; +import ru.abstractmenus.api.handler.PermissionsHandler; +import ru.abstractmenus.api.handler.PlaceholderHandler; +import ru.abstractmenus.api.handler.SkinHandler; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; + +/** + * Default {@link ProviderRegistry} implementation. Five sections sharing an + * inner generic {@link Section} class. Insertion-ordered per section so that + * equal-priority ties resolve to the first-registered entry. + * + *

Thread-safe for registration/unregistration via per-section synchronized + * methods, but production use expects all mutation to happen on the main + * server thread during plugin / extension enable/disable. + */ +public final class ProviderRegistryImpl implements ProviderRegistry { + + private final Section economy = new Section<>(); + private final Section permissions = new Section<>(); + private final Section levels = new Section<>(); + private final Section placeholders = new Section<>(); + private final Section skins = new Section<>(); + + /** section kind → configured-default id (e.g. "economy" → "playerpoints"). */ + private Function configDefaults = kind -> null; // no-op by default + + /** Wire up the config-backed default source. Called once from AbstractMenusApiImpl. */ + public void setConfigDefaults(Function lookup) { + this.configDefaults = lookup; + } + + // ---- Config-default resolution helper -------------------------------- + + private T resolveWithConfig(String kind, Section section) { + String configured = configDefaults.apply(kind); + if (configured != null && !configured.equalsIgnoreCase("auto")) { + T h = section.byId(configured); + if (h != null) return h; + // Configured id not found — fall back to auto. + } + return section.auto(); + } + + // ---- Economy --------------------------------------------------------- + + @Override public void registerEconomy(String id, EconomyHandler h, int pr, MenuExtension o) { economy.put(id, h, pr, o); } + @Override public EconomyHandler economy() { return resolveWithConfig("economy", economy); } + @Override public EconomyHandler economy(String id) { return economy.byId(id); } + @Override public Collection allEconomy() { return economy.all(); } + @Override public boolean hasEconomy(String id) { return economy.has(id); } + + // ---- Permissions ----------------------------------------------------- + + @Override public void registerPermissions(String id, PermissionsHandler h, int pr, MenuExtension o) { permissions.put(id, h, pr, o); } + @Override public PermissionsHandler permissions() { return resolveWithConfig("permissions", permissions); } + @Override public PermissionsHandler permissions(String id) { return permissions.byId(id); } + @Override public Collection allPermissions() { return permissions.all(); } + @Override public boolean hasPermissions(String id) { return permissions.has(id); } + + // ---- Levels ---------------------------------------------------------- + + @Override public void registerLevels(String id, LevelHandler h, int pr, MenuExtension o) { levels.put(id, h, pr, o); } + @Override public LevelHandler levels() { return resolveWithConfig("levels", levels); } + @Override public LevelHandler levels(String id) { return levels.byId(id); } + @Override public Collection allLevels() { return levels.all(); } + @Override public boolean hasLevels(String id) { return levels.has(id); } + + // ---- Placeholders ---------------------------------------------------- + + @Override public void registerPlaceholders(String id, PlaceholderHandler h, int pr, MenuExtension o) { placeholders.put(id, h, pr, o); } + @Override public PlaceholderHandler placeholders() { return resolveWithConfig("placeholders", placeholders); } + @Override public PlaceholderHandler placeholders(String id) { return placeholders.byId(id); } + @Override public Collection allPlaceholders() { return placeholders.all(); } + @Override public boolean hasPlaceholders(String id) { return placeholders.has(id); } + + // ---- Skins ----------------------------------------------------------- + + @Override public void registerSkins(String id, SkinHandler h, int pr, MenuExtension o) { skins.put(id, h, pr, o); } + @Override public SkinHandler skins() { return resolveWithConfig("skins", skins); } + @Override public SkinHandler skins(String id) { return skins.byId(id); } + @Override public Collection allSkins() { return skins.all(); } + @Override public boolean hasSkins(String id) { return skins.has(id); } + + // ---- Cleanup --------------------------------------------------------- + + @Override + public void unregisterAll(MenuExtension owner) { + economy.unregisterAll(owner); + permissions.unregisterAll(owner); + levels.unregisterAll(owner); + placeholders.unregisterAll(owner); + skins.unregisterAll(owner); + } + + // ---- Inner section --------------------------------------------------- + + private static final class Section { + private final Map> byId = new LinkedHashMap<>(); + private final Map> keysByOwner = new IdentityHashMap<>(); + + synchronized void put(String id, T handler, int priority, MenuExtension owner) { + String k = id.toLowerCase(); + byId.put(k, new Entry<>(handler, priority)); + keysByOwner.computeIfAbsent(owner, o -> new HashSet<>()).add(k); + } + + synchronized T byId(String id) { + Entry e = byId.get(id.toLowerCase()); + return e == null ? null : e.handler; + } + + synchronized boolean has(String id) { + return byId.containsKey(id.toLowerCase()); + } + + synchronized T auto() { + Entry best = null; + for (Entry e : byId.values()) { + if (best == null || e.priority > best.priority) best = e; + } + return best == null ? null : best.handler; + } + + synchronized Collection all() { + List list = new ArrayList<>(byId.size()); + for (Entry e : byId.values()) list.add(e.handler); + return Collections.unmodifiableList(list); + } + + synchronized void unregisterAll(MenuExtension owner) { + Set keys = keysByOwner.remove(owner); + if (keys == null) return; + for (String k : keys) byId.remove(k); + } + + private static final class Entry { + final T handler; + final int priority; + Entry(T handler, int priority) { this.handler = handler; this.priority = priority; } + } + } +} diff --git a/plugin/src/main/java/ru/abstractmenus/api/TypeRegistryImpl.java b/plugin/src/main/java/ru/abstractmenus/api/TypeRegistryImpl.java new file mode 100644 index 0000000..225e081 --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/api/TypeRegistryImpl.java @@ -0,0 +1,100 @@ +package ru.abstractmenus.api; + +import ru.abstractmenus.hocon.api.serialize.NodeSerializer; +import ru.abstractmenus.hocon.api.serialize.NodeSerializers; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Set; +import java.util.logging.Logger; + +/** + * In-memory {@link TypeRegistry} implementation. Not thread-safe for + * concurrent registration; consumers must register/unregister from the main + * server thread. + * + *

Note on {@code NodeSerializers.unregister}: hocon 1.0.6 does NOT expose + * an {@code unregister(Class)} method. Therefore stale serializer entries + * survive in {@link NodeSerializers} after {@link #unregisterAll(MenuExtension)}, + * but that is harmless — the {@link #byKey} map is the authoritative lookup + * table, and a subsequent {@link #register} call for the same class token + * overwrites the serializer entry. + */ +public final class TypeRegistryImpl implements TypeRegistry { + + private static final Logger LOG = Logger.getLogger(TypeRegistryImpl.class.getName()); + + private final NodeSerializers serializers; + + /** key (lowercased) → registered class */ + private final Map> byKey = new HashMap<>(); + + /** class → key (reverse index, kept in sync with {@link #byKey}) */ + private final Map, String> byType = new IdentityHashMap<>(); + + /** owner → set of keys they registered (for unregisterAll) */ + private final Map> keysByOwner = new IdentityHashMap<>(); + + public TypeRegistryImpl(NodeSerializers serializers) { + this.serializers = serializers; + } + + @Override + public synchronized void register(String key, + Class type, + NodeSerializer serializer, + MenuExtension owner) { + String k = key.toLowerCase(); + + Class existing = byKey.get(k); + if (existing != null) { + LOG.warning("TypeRegistry: overwriting existing entry '" + k + + "' (" + existing.getName() + " -> " + type.getName() + ")"); + byType.remove(existing); + // Intentionally do not remove from owner tracking — the old owner + // no longer has this key since it's overwritten; cleanup of their + // orphan entries happens on their own unregisterAll. + } + + byKey.put(k, type); + byType.put(type, k); + serializers.register(type, serializer); + + keysByOwner.computeIfAbsent(owner, o -> new HashSet<>()).add(k); + } + + @Override + public synchronized Class get(String key) { + return byKey.get(key.toLowerCase()); + } + + @Override + public synchronized String name(Class type) { + return byType.get(type); + } + + @Override + public synchronized Set keys() { + return Collections.unmodifiableSet(new HashSet<>(byKey.keySet())); + } + + @Override + public synchronized void unregisterAll(MenuExtension owner) { + Set keys = keysByOwner.remove(owner); + if (keys == null) return; + + for (String k : keys) { + Class type = byKey.remove(k); + if (type != null) { + byType.remove(type); + // NodeSerializers.unregister(Class) does not exist in hocon 1.0.6. + // The stale serializer entry in NodeSerializers is harmless — + // byKey is authoritative, and re-registration overwrites it. + // serializers.unregister(type); + } + } + } +} diff --git a/src/main/java/ru/abstractmenus/command/ArgumentParseException.java b/plugin/src/main/java/ru/abstractmenus/command/ArgumentParseException.java similarity index 100% rename from src/main/java/ru/abstractmenus/command/ArgumentParseException.java rename to plugin/src/main/java/ru/abstractmenus/command/ArgumentParseException.java diff --git a/src/main/java/ru/abstractmenus/command/Command.java b/plugin/src/main/java/ru/abstractmenus/command/Command.java similarity index 97% rename from src/main/java/ru/abstractmenus/command/Command.java rename to plugin/src/main/java/ru/abstractmenus/command/Command.java index df5f263..742f805 100644 --- a/src/main/java/ru/abstractmenus/command/Command.java +++ b/plugin/src/main/java/ru/abstractmenus/command/Command.java @@ -4,7 +4,7 @@ import lombok.Setter; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.text.Colors; import ru.abstractmenus.command.args.Argument; import ru.abstractmenus.hocon.api.ConfigNode; @@ -68,7 +68,7 @@ public void execute(CommandSender sender, List args) { } catch (Throwable t) { if (arg.getDef() != null && sender instanceof Player) { Player player = (Player) sender; - String def = Handlers.getPlaceholderHandler().replace(player, arg.getDef()); + String def = AbstractMenusApi.get().providers().placeholders().replace(player, arg.getDef()); try { Object defObj = arg.parse(sender, def); diff --git a/src/main/java/ru/abstractmenus/command/CommandContext.java b/plugin/src/main/java/ru/abstractmenus/command/CommandContext.java similarity index 100% rename from src/main/java/ru/abstractmenus/command/CommandContext.java rename to plugin/src/main/java/ru/abstractmenus/command/CommandContext.java diff --git a/src/main/java/ru/abstractmenus/command/CommandHandler.java b/plugin/src/main/java/ru/abstractmenus/command/CommandHandler.java similarity index 100% rename from src/main/java/ru/abstractmenus/command/CommandHandler.java rename to plugin/src/main/java/ru/abstractmenus/command/CommandHandler.java diff --git a/src/main/java/ru/abstractmenus/command/CommandManager.java b/plugin/src/main/java/ru/abstractmenus/command/CommandManager.java similarity index 100% rename from src/main/java/ru/abstractmenus/command/CommandManager.java rename to plugin/src/main/java/ru/abstractmenus/command/CommandManager.java diff --git a/src/main/java/ru/abstractmenus/command/DefaultHandler.java b/plugin/src/main/java/ru/abstractmenus/command/DefaultHandler.java similarity index 100% rename from src/main/java/ru/abstractmenus/command/DefaultHandler.java rename to plugin/src/main/java/ru/abstractmenus/command/DefaultHandler.java diff --git a/src/main/java/ru/abstractmenus/command/args/Argument.java b/plugin/src/main/java/ru/abstractmenus/command/args/Argument.java similarity index 100% rename from src/main/java/ru/abstractmenus/command/args/Argument.java rename to plugin/src/main/java/ru/abstractmenus/command/args/Argument.java diff --git a/src/main/java/ru/abstractmenus/command/args/ChoiceArgument.java b/plugin/src/main/java/ru/abstractmenus/command/args/ChoiceArgument.java similarity index 100% rename from src/main/java/ru/abstractmenus/command/args/ChoiceArgument.java rename to plugin/src/main/java/ru/abstractmenus/command/args/ChoiceArgument.java diff --git a/src/main/java/ru/abstractmenus/command/args/IntegerArgument.java b/plugin/src/main/java/ru/abstractmenus/command/args/IntegerArgument.java similarity index 100% rename from src/main/java/ru/abstractmenus/command/args/IntegerArgument.java rename to plugin/src/main/java/ru/abstractmenus/command/args/IntegerArgument.java diff --git a/src/main/java/ru/abstractmenus/command/args/NumberArgument.java b/plugin/src/main/java/ru/abstractmenus/command/args/NumberArgument.java similarity index 100% rename from src/main/java/ru/abstractmenus/command/args/NumberArgument.java rename to plugin/src/main/java/ru/abstractmenus/command/args/NumberArgument.java diff --git a/src/main/java/ru/abstractmenus/command/args/PlayerArgument.java b/plugin/src/main/java/ru/abstractmenus/command/args/PlayerArgument.java similarity index 100% rename from src/main/java/ru/abstractmenus/command/args/PlayerArgument.java rename to plugin/src/main/java/ru/abstractmenus/command/args/PlayerArgument.java diff --git a/src/main/java/ru/abstractmenus/command/args/StringArgument.java b/plugin/src/main/java/ru/abstractmenus/command/args/StringArgument.java similarity index 100% rename from src/main/java/ru/abstractmenus/command/args/StringArgument.java rename to plugin/src/main/java/ru/abstractmenus/command/args/StringArgument.java diff --git a/src/main/java/ru/abstractmenus/command/bukkit/CommandWrapper.java b/plugin/src/main/java/ru/abstractmenus/command/bukkit/CommandWrapper.java similarity index 100% rename from src/main/java/ru/abstractmenus/command/bukkit/CommandWrapper.java rename to plugin/src/main/java/ru/abstractmenus/command/bukkit/CommandWrapper.java diff --git a/src/main/java/ru/abstractmenus/commands/AbstractMenuCommand.java b/plugin/src/main/java/ru/abstractmenus/commands/AbstractMenuCommand.java similarity index 91% rename from src/main/java/ru/abstractmenus/commands/AbstractMenuCommand.java rename to plugin/src/main/java/ru/abstractmenus/commands/AbstractMenuCommand.java index 1b136bd..60fc805 100644 --- a/src/main/java/ru/abstractmenus/commands/AbstractMenuCommand.java +++ b/plugin/src/main/java/ru/abstractmenus/commands/AbstractMenuCommand.java @@ -13,7 +13,8 @@ public AbstractMenuCommand(String permission) { Colors.of("&7/am open &e- Open menu with specific name for player"), Colors.of("&7/am reload &e- Reload all menu files. Also close all opened menus for player"), Colors.of("&7/am serve &e- Turn on/off automatic menu reloading if file changed"), - Colors.of("&7/am version &e- show plugin version") + Colors.of("&7/am version &e- show plugin version"), + Colors.of("&7/am addons list|reload|info|load|rescan &e- manage AM-loaded addons") ); } diff --git a/src/main/java/ru/abstractmenus/commands/Command.java b/plugin/src/main/java/ru/abstractmenus/commands/Command.java similarity index 57% rename from src/main/java/ru/abstractmenus/commands/Command.java rename to plugin/src/main/java/ru/abstractmenus/commands/Command.java index b5e4625..ce9a88f 100644 --- a/src/main/java/ru/abstractmenus/commands/Command.java +++ b/plugin/src/main/java/ru/abstractmenus/commands/Command.java @@ -3,14 +3,18 @@ import lombok.Getter; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Stack; -public abstract class Command implements CommandExecutor { +public abstract class Command implements CommandExecutor, TabCompleter { @Getter private String permission; @@ -96,4 +100,41 @@ private boolean checkPermission(CommandSender sender, Command command) { public abstract void execute(CommandSender sender, String[] args); + @Override + public @NotNull List onTabComplete(@NotNull CommandSender sender, @NotNull org.bukkit.command.Command cmd, @NotNull String alias, @NotNull String[] args) { + if (!checkPermission(sender, this)) return Collections.emptyList(); + return tabComplete(sender, args); + } + + /** + * Recursive tab-completion entry point. Default behaviour: if the + * user is typing the first arg, return registered subcommand keys + * matching the current prefix; if a subcommand has already been + * named, drill into it and forward {@code args[1..]}. Subclasses + * override this method to add custom completions for typed values + * (e.g. addon names, menu names, online players). + * + *

Suggestions are only returned for the *last* arg in {@code args} + * (the one Bukkit considers the "in-progress" token). + */ + public List tabComplete(CommandSender sender, String[] args) { + if (args.length == 0) return Collections.emptyList(); + if (args.length == 1) { + String prefix = args[0].toLowerCase(); + List result = new ArrayList<>(); + for (Map.Entry e : subCommands.entrySet()) { + if (!checkPermission(sender, e.getValue())) continue; + if (e.getKey().toLowerCase().startsWith(prefix)) { + result.add(e.getKey()); + } + } + Collections.sort(result); + return result; + } + Command sub = getSub(args[0]); + if (sub == null) return Collections.emptyList(); + if (!checkPermission(sender, sub)) return Collections.emptyList(); + return sub.tabComplete(sender, Arrays.copyOfRange(args, 1, args.length)); + } + } diff --git a/src/main/java/ru/abstractmenus/commands/VarCommand.java b/plugin/src/main/java/ru/abstractmenus/commands/VarCommand.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/VarCommand.java rename to plugin/src/main/java/ru/abstractmenus/commands/VarCommand.java diff --git a/src/main/java/ru/abstractmenus/commands/VarpCommand.java b/plugin/src/main/java/ru/abstractmenus/commands/VarpCommand.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/VarpCommand.java rename to plugin/src/main/java/ru/abstractmenus/commands/VarpCommand.java diff --git a/plugin/src/main/java/ru/abstractmenus/commands/am/CommandAddons.java b/plugin/src/main/java/ru/abstractmenus/commands/am/CommandAddons.java new file mode 100644 index 0000000..54e4f57 --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/commands/am/CommandAddons.java @@ -0,0 +1,208 @@ +package ru.abstractmenus.commands.am; + +import org.bukkit.command.CommandSender; +import ru.abstractmenus.AbstractMenus; +import ru.abstractmenus.addon.AddonManager; +import ru.abstractmenus.addon.AddonStatus; +import ru.abstractmenus.addon.LoadedAddon; +import ru.abstractmenus.api.text.Colors; +import ru.abstractmenus.commands.Command; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** {@code /am addons [list|reload |info |load |rescan]} */ +public class CommandAddons extends Command { + + private static final List SUBCOMMANDS = + List.of("list", "reload", "info", "load", "rescan"); + + public CommandAddons() { + setUsage( + Colors.of("&7/am addons list &e- list all AM-loaded addons"), + Colors.of("&7/am addons reload &e- reload a single loaded addon"), + Colors.of("&7/am addons info &e- show addon metadata"), + Colors.of("&7/am addons load &e- load a new addon dropped at runtime"), + Colors.of("&7/am addons rescan &e- scan addons/ and load any new jars") + ); + } + + @Override + public void execute(CommandSender sender, String[] args) { + AddonManager am = AbstractMenus.instance().getAddonManager(); + if (am == null) { + sender.sendMessage(Colors.of("&cAddonManager not yet initialised.")); + return; + } + + if (args.length == 0) { + sender.sendMessage(getUsage()); + return; + } + + switch (args[0].toLowerCase()) { + case "list" -> list(sender, am); + case "reload" -> reload(sender, am, args); + case "info" -> info(sender, am, args); + case "load" -> load(sender, am, args); + case "rescan" -> rescan(sender, am); + default -> sender.sendMessage(getUsage()); + } + } + + private void list(CommandSender sender, AddonManager am) { + var addons = am.loaded(); + if (addons.isEmpty()) { + sender.sendMessage(Colors.of("&7No AM-loaded addons.")); + return; + } + sender.sendMessage(Colors.of("&e&lAddons (" + addons.size() + "):")); + for (LoadedAddon la : addons) { + String color = switch (la.getStatus()) { + case ENABLED -> "&a"; + case DISABLED -> "&7"; + case FAILED -> "&c"; + case PENDING -> "&e"; + }; + sender.sendMessage(Colors.of(color + " " + la.getConf().name() + + " &8v" + la.getConf().version() + + " &7[" + la.getStatus() + "]")); + } + } + + private void reload(CommandSender sender, AddonManager am, String[] args) { + if (args.length < 2) { + sender.sendMessage(Colors.of("&cUsage: /am addons reload ")); + return; + } + String name = args[1]; + var result = am.reload(name); + if (result.isEmpty()) { + sender.sendMessage(Colors.of("&cAddon '" + name + "' not found or no jar present.")); + return; + } + LoadedAddon la = result.get(); + if (la.getStatus() == AddonStatus.ENABLED) { + sender.sendMessage(Colors.of("&aReloaded " + la.getConf().name() + ".")); + } else { + sender.sendMessage(Colors.of("&cReload failed: " + + (la.getError() == null ? "unknown error" : la.getError().getMessage()))); + } + } + + private void info(CommandSender sender, AddonManager am, String[] args) { + if (args.length < 2) { + sender.sendMessage(Colors.of("&cUsage: /am addons info ")); + return; + } + var opt = am.get(args[1]); + if (opt.isEmpty()) { + sender.sendMessage(Colors.of("&cAddon '" + args[1] + "' not found.")); + return; + } + LoadedAddon la = opt.get(); + var c = la.getConf(); + sender.sendMessage(Colors.of("&e&l" + c.name() + " &7v" + c.version())); + sender.sendMessage(Colors.of("&7 status: &f" + la.getStatus())); + if (!c.authors().isEmpty()) { + sender.sendMessage(Colors.of("&7 authors: &f" + String.join(", ", c.authors()))); + } + if (!c.description().isEmpty()) { + sender.sendMessage(Colors.of("&7 description: &f" + c.description())); + } + if (c.targetApiVersion() != null) { + sender.sendMessage(Colors.of("&7 targetApiVersion: &f" + c.targetApiVersion())); + } + if (!c.addonDependencies().isEmpty()) { + sender.sendMessage(Colors.of("&7 addonDependencies: &f" + + String.join(", ", c.addonDependencies()))); + } + if (!c.pluginDependencies().isEmpty()) { + sender.sendMessage(Colors.of("&7 pluginDependencies: &f" + + String.join(", ", c.pluginDependencies()))); + } + if (la.getStatus() == AddonStatus.FAILED && la.getError() != null) { + sender.sendMessage(Colors.of("&7 error: &c" + la.getError().getMessage())); + } + } + + private void load(CommandSender sender, AddonManager am, String[] args) { + if (args.length < 2) { + sender.sendMessage(Colors.of("&cUsage: /am addons load ")); + return; + } + String name = args[1]; + var result = am.loadOne(name); + if (result.isEmpty()) { + sender.sendMessage(Colors.of("&cNo unloaded addon named '" + name + "' found in addons/. " + + "Check the jar is in plugins/AbstractMenus/addons/ and addon.conf names it correctly.")); + return; + } + LoadedAddon la = result.get(); + if (la.getStatus() == AddonStatus.ENABLED) { + sender.sendMessage(Colors.of("&aLoaded " + la.getConf().name() + + " v" + la.getConf().version() + ".")); + } else { + sender.sendMessage(Colors.of("&cLoad failed: " + + (la.getError() == null ? "unknown error" : la.getError().getMessage()))); + } + } + + private void rescan(CommandSender sender, AddonManager am) { + var newlyLoaded = am.rescan(); + if (newlyLoaded.isEmpty()) { + sender.sendMessage(Colors.of("&7No new addons found.")); + return; + } + long enabled = newlyLoaded.stream() + .filter(la -> la.getStatus() == AddonStatus.ENABLED).count(); + long failed = newlyLoaded.size() - enabled; + sender.sendMessage(Colors.of("&aRescan: " + enabled + " loaded" + + (failed > 0 ? ", &c" + failed + " failed" : "") + "&a.")); + for (LoadedAddon la : newlyLoaded) { + String color = la.getStatus() == AddonStatus.ENABLED ? "&a" : "&c"; + sender.sendMessage(Colors.of(color + " " + la.getConf().name() + + " &8v" + la.getConf().version() + + " &7[" + la.getStatus() + "]")); + } + } + + @Override + public List tabComplete(CommandSender sender, String[] args) { + if (args.length == 0) return Collections.emptyList(); + + if (args.length == 1) { + String prefix = args[0].toLowerCase(); + List result = new ArrayList<>(); + for (String s : SUBCOMMANDS) { + if (s.startsWith(prefix)) result.add(s); + } + return result; + } + + if (args.length == 2) { + AddonManager am = AbstractMenus.instance().getAddonManager(); + if (am == null) return Collections.emptyList(); + String prefix = args[1].toLowerCase(); + return switch (args[0].toLowerCase()) { + case "reload", "info" -> filterByPrefix( + am.loaded().stream().map(la -> la.getConf().name()).toList(), + prefix); + case "load" -> filterByPrefix(am.availableNotLoaded(), prefix); + default -> Collections.emptyList(); + }; + } + + return Collections.emptyList(); + } + + private static List filterByPrefix(List source, String prefix) { + List result = new ArrayList<>(); + for (String s : source) { + if (s.toLowerCase().startsWith(prefix)) result.add(s); + } + Collections.sort(result); + return result; + } +} diff --git a/src/main/java/ru/abstractmenus/commands/am/CommandOpen.java b/plugin/src/main/java/ru/abstractmenus/commands/am/CommandOpen.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/am/CommandOpen.java rename to plugin/src/main/java/ru/abstractmenus/commands/am/CommandOpen.java diff --git a/src/main/java/ru/abstractmenus/commands/am/CommandPluginVersion.java b/plugin/src/main/java/ru/abstractmenus/commands/am/CommandPluginVersion.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/am/CommandPluginVersion.java rename to plugin/src/main/java/ru/abstractmenus/commands/am/CommandPluginVersion.java diff --git a/src/main/java/ru/abstractmenus/commands/am/CommandReload.java b/plugin/src/main/java/ru/abstractmenus/commands/am/CommandReload.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/am/CommandReload.java rename to plugin/src/main/java/ru/abstractmenus/commands/am/CommandReload.java diff --git a/src/main/java/ru/abstractmenus/commands/am/CommandServe.java b/plugin/src/main/java/ru/abstractmenus/commands/am/CommandServe.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/am/CommandServe.java rename to plugin/src/main/java/ru/abstractmenus/commands/am/CommandServe.java diff --git a/src/main/java/ru/abstractmenus/commands/var/VarDec.java b/plugin/src/main/java/ru/abstractmenus/commands/var/VarDec.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/var/VarDec.java rename to plugin/src/main/java/ru/abstractmenus/commands/var/VarDec.java diff --git a/src/main/java/ru/abstractmenus/commands/var/VarDiv.java b/plugin/src/main/java/ru/abstractmenus/commands/var/VarDiv.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/var/VarDiv.java rename to plugin/src/main/java/ru/abstractmenus/commands/var/VarDiv.java diff --git a/src/main/java/ru/abstractmenus/commands/var/VarGet.java b/plugin/src/main/java/ru/abstractmenus/commands/var/VarGet.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/var/VarGet.java rename to plugin/src/main/java/ru/abstractmenus/commands/var/VarGet.java diff --git a/src/main/java/ru/abstractmenus/commands/var/VarInc.java b/plugin/src/main/java/ru/abstractmenus/commands/var/VarInc.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/var/VarInc.java rename to plugin/src/main/java/ru/abstractmenus/commands/var/VarInc.java diff --git a/src/main/java/ru/abstractmenus/commands/var/VarMul.java b/plugin/src/main/java/ru/abstractmenus/commands/var/VarMul.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/var/VarMul.java rename to plugin/src/main/java/ru/abstractmenus/commands/var/VarMul.java diff --git a/src/main/java/ru/abstractmenus/commands/var/VarRem.java b/plugin/src/main/java/ru/abstractmenus/commands/var/VarRem.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/var/VarRem.java rename to plugin/src/main/java/ru/abstractmenus/commands/var/VarRem.java diff --git a/src/main/java/ru/abstractmenus/commands/var/VarSet.java b/plugin/src/main/java/ru/abstractmenus/commands/var/VarSet.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/var/VarSet.java rename to plugin/src/main/java/ru/abstractmenus/commands/var/VarSet.java diff --git a/src/main/java/ru/abstractmenus/commands/varp/VarpDec.java b/plugin/src/main/java/ru/abstractmenus/commands/varp/VarpDec.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/varp/VarpDec.java rename to plugin/src/main/java/ru/abstractmenus/commands/varp/VarpDec.java diff --git a/src/main/java/ru/abstractmenus/commands/varp/VarpDiv.java b/plugin/src/main/java/ru/abstractmenus/commands/varp/VarpDiv.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/varp/VarpDiv.java rename to plugin/src/main/java/ru/abstractmenus/commands/varp/VarpDiv.java diff --git a/src/main/java/ru/abstractmenus/commands/varp/VarpGet.java b/plugin/src/main/java/ru/abstractmenus/commands/varp/VarpGet.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/varp/VarpGet.java rename to plugin/src/main/java/ru/abstractmenus/commands/varp/VarpGet.java diff --git a/src/main/java/ru/abstractmenus/commands/varp/VarpInc.java b/plugin/src/main/java/ru/abstractmenus/commands/varp/VarpInc.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/varp/VarpInc.java rename to plugin/src/main/java/ru/abstractmenus/commands/varp/VarpInc.java diff --git a/src/main/java/ru/abstractmenus/commands/varp/VarpMul.java b/plugin/src/main/java/ru/abstractmenus/commands/varp/VarpMul.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/varp/VarpMul.java rename to plugin/src/main/java/ru/abstractmenus/commands/varp/VarpMul.java diff --git a/src/main/java/ru/abstractmenus/commands/varp/VarpRem.java b/plugin/src/main/java/ru/abstractmenus/commands/varp/VarpRem.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/varp/VarpRem.java rename to plugin/src/main/java/ru/abstractmenus/commands/varp/VarpRem.java diff --git a/src/main/java/ru/abstractmenus/commands/varp/VarpSet.java b/plugin/src/main/java/ru/abstractmenus/commands/varp/VarpSet.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/varp/VarpSet.java rename to plugin/src/main/java/ru/abstractmenus/commands/varp/VarpSet.java diff --git a/plugin/src/main/java/ru/abstractmenus/core/CoreActionsBundle.java b/plugin/src/main/java/ru/abstractmenus/core/CoreActionsBundle.java new file mode 100644 index 0000000..f604a43 --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/core/CoreActionsBundle.java @@ -0,0 +1,144 @@ +package ru.abstractmenus.core; + +import ru.abstractmenus.api.AbstractMenusApi; +import ru.abstractmenus.api.MenuExtension; +import ru.abstractmenus.data.actions.var.*; +import ru.abstractmenus.data.actions.varp.*; +import ru.abstractmenus.data.actions.wrappers.ActionBulk; +import ru.abstractmenus.data.actions.wrappers.ActionDelay; +import ru.abstractmenus.data.actions.wrappers.ActionPlayerScope; +import ru.abstractmenus.data.actions.wrappers.ActionRandomActions; +import ru.abstractmenus.data.actions.ActionMenuClose; +import ru.abstractmenus.data.actions.ActionMenuOpen; +import ru.abstractmenus.data.actions.ActionMenuOpenCtx; +import ru.abstractmenus.data.actions.ActionGroupAdd; +import ru.abstractmenus.data.actions.ActionBungeeConnect; +import ru.abstractmenus.data.actions.ActionMenuRefresh; +import ru.abstractmenus.data.actions.ActionCommand; +import ru.abstractmenus.data.actions.ActionLevelGive; +import ru.abstractmenus.data.actions.ActionMoneyGive; +import ru.abstractmenus.data.actions.ActionPermissionGive; +import ru.abstractmenus.data.actions.ActionLuckPermsMetaSet; +import ru.abstractmenus.data.actions.ActionLuckPermsMetaRemove; +import ru.abstractmenus.data.actions.ActionXpGive; +import ru.abstractmenus.data.actions.ActionItemAdd; +import ru.abstractmenus.data.actions.ActionItemRemove; +import ru.abstractmenus.data.actions.ActionItemClear; +import ru.abstractmenus.data.actions.ActionInventoryClear; +import ru.abstractmenus.data.actions.ActionMessage; +import ru.abstractmenus.data.actions.ActionBroadcast; +import ru.abstractmenus.data.actions.ActionMiniMessage; +import ru.abstractmenus.data.actions.ActionPotionEffect; +import ru.abstractmenus.data.actions.ActionPotionEffectRemove; +import ru.abstractmenus.data.actions.ActionGroupRemove; +import ru.abstractmenus.data.actions.ActionPermissionRemove; +import ru.abstractmenus.data.actions.ActionFoodLevelSet; +import ru.abstractmenus.data.actions.ActionHealthSet; +import ru.abstractmenus.data.actions.ActionSound; +import ru.abstractmenus.data.actions.ActionSoundCustom; +import ru.abstractmenus.data.actions.ActionLevelTake; +import ru.abstractmenus.data.actions.ActionMoneyTake; +import ru.abstractmenus.data.actions.ActionXpTake; +import ru.abstractmenus.data.actions.ActionTeleport; +import ru.abstractmenus.data.actions.ActionBookOpen; +import ru.abstractmenus.data.actions.ActionSkinSet; +import ru.abstractmenus.data.actions.ActionSkinReset; +import ru.abstractmenus.data.actions.ActionRecipeAdd; +import ru.abstractmenus.data.actions.ActionPageNext; +import ru.abstractmenus.data.actions.ActionPagePrev; +import ru.abstractmenus.data.actions.ActionPropertySet; +import ru.abstractmenus.data.actions.ActionPropertyRemove; +import ru.abstractmenus.data.actions.ActionItemRefresh; +import ru.abstractmenus.data.actions.ActionPlayerChat; +import ru.abstractmenus.data.actions.ActionGameModeSet; +import ru.abstractmenus.data.actions.ActionInputChat; +import ru.abstractmenus.data.actions.ActionButtonSet; +import ru.abstractmenus.data.actions.ActionButtonRemove; +import ru.abstractmenus.data.actions.ActionPlacedItemRemove; +import ru.abstractmenus.data.actions.ActionPlaceItem; +import ru.abstractmenus.data.actions.ActionLog; + +/** + * Core action registrations. Mirrors {@code MenuActions.init()} before the + * SPI refactor. + */ +final class CoreActionsBundle { + + void register(AbstractMenusApi api, MenuExtension owner) { + api.actions().register("closeMenu", ActionMenuClose.class, new ActionMenuClose.Serializer(), owner); + api.actions().register("openMenu", ActionMenuOpen.class, new ActionMenuOpen.Serializer(), owner); + api.actions().register("openMenuCtx", ActionMenuOpenCtx.class, new ActionMenuOpenCtx.Serializer(), owner); + api.actions().register("playerScope", ActionPlayerScope.class, new ActionPlayerScope.Serializer(), owner); + + api.actions().register("addGroup", ActionGroupAdd.class, new ActionGroupAdd.Serializer(), owner); + api.actions().register("bungeeConnect", ActionBungeeConnect.class, new ActionBungeeConnect.Serializer(), owner); + api.actions().register("refreshMenu", ActionMenuRefresh.class, new ActionMenuRefresh.Serializer(), owner); + api.actions().register("command", ActionCommand.class, new ActionCommand.Serializer(), owner); + api.actions().register("giveLevel", ActionLevelGive.class, new ActionLevelGive.Serializer(), owner); + api.actions().register("giveMoney", ActionMoneyGive.class, new ActionMoneyGive.Serializer(), owner); + api.actions().register("givePermission", ActionPermissionGive.class, new ActionPermissionGive.Serializer(), owner); + api.actions().register("lpMetaSet", ActionLuckPermsMetaSet.class, new ActionLuckPermsMetaSet.Serializer(), owner); + api.actions().register("lpMetaRemove", ActionLuckPermsMetaRemove.class, new ActionLuckPermsMetaRemove.Serializer(), owner); + api.actions().register("giveXp", ActionXpGive.class, new ActionXpGive.Serializer(), owner); + api.actions().register("itemAdd", ActionItemAdd.class, new ActionItemAdd.Serializer(), owner); + api.actions().register("itemRemove", ActionItemRemove.class, new ActionItemRemove.Serializer(), owner); + api.actions().register("itemClear", ActionItemClear.class, new ActionItemClear.Serializer(), owner); + api.actions().register("inventoryClear", ActionInventoryClear.class, new ActionInventoryClear.Serializer(), owner); + api.actions().register("message", ActionMessage.class, new ActionMessage.Serializer(), owner); + api.actions().register("broadcast", ActionBroadcast.class, new ActionBroadcast.Serializer(), owner); + api.actions().register("miniMessage", ActionMiniMessage.class, new ActionMiniMessage.Serializer(), owner); + api.actions().register("potionEffect", ActionPotionEffect.class, new ActionPotionEffect.Serializer(), owner); + api.actions().register("removePotionEffect", ActionPotionEffectRemove.class, new ActionPotionEffectRemove.Serializer(), owner); + api.actions().register("removeGroup", ActionGroupRemove.class, new ActionGroupRemove.Serializer(), owner); + api.actions().register("removePermission", ActionPermissionRemove.class, new ActionPermissionRemove.Serializer(), owner); + api.actions().register("setFoodLevel", ActionFoodLevelSet.class, new ActionFoodLevelSet.Serializer(), owner); + api.actions().register("setHealth", ActionHealthSet.class, new ActionHealthSet.Serializer(), owner); + api.actions().register("sound", ActionSound.class, new ActionSound.Serializer(), owner); + + try { + // SoundCategory missing on legacy Bukkit + api.actions().register("customSound", ActionSoundCustom.class, new ActionSoundCustom.Serializer(), owner); + } catch (Throwable ignore) { + } + + api.actions().register("takeLevel", ActionLevelTake.class, new ActionLevelTake.Serializer(), owner); + api.actions().register("takeMoney", ActionMoneyTake.class, new ActionMoneyTake.Serializer(), owner); + api.actions().register("takeXp", ActionXpTake.class, new ActionXpTake.Serializer(), owner); + api.actions().register("teleport", ActionTeleport.class, new ActionTeleport.Serializer(), owner); + api.actions().register("openBook", ActionBookOpen.class, new ActionBookOpen.Serializer(), owner); + api.actions().register("delay", ActionDelay.class, new ActionDelay.Serializer(), owner); + api.actions().register("setSkin", ActionSkinSet.class, new ActionSkinSet.Serializer(), owner); + api.actions().register("resetSkin", ActionSkinReset.class, new ActionSkinReset.Serializer(), owner); + api.actions().register("addRecipe", ActionRecipeAdd.class, new ActionRecipeAdd.Serializer(), owner); + api.actions().register("pageNext", ActionPageNext.class, new ActionPageNext.Serializer(), owner); + api.actions().register("pagePrev", ActionPagePrev.class, new ActionPagePrev.Serializer(), owner); + api.actions().register("bulk", ActionBulk.class, new ActionBulk.Serializer(), owner); + api.actions().register("setProperty", ActionPropertySet.class, new ActionPropertySet.Serializer(), owner); + api.actions().register("remProperty", ActionPropertyRemove.class, new ActionPropertyRemove.Serializer(), owner); + api.actions().register("refreshItem", ActionItemRefresh.class, new ActionItemRefresh.Serializer(), owner); + api.actions().register("randActions", ActionRandomActions.class, new ActionRandomActions.Serializer(), owner); + api.actions().register("playerChat", ActionPlayerChat.class, new ActionPlayerChat.Serializer(), owner); + api.actions().register("setGamemode", ActionGameModeSet.class, new ActionGameModeSet.Serializer(), owner); + api.actions().register("inputChat", ActionInputChat.class, new ActionInputChat.Serializer(), owner); + api.actions().register("setButton", ActionButtonSet.class, new ActionButtonSet.Serializer(), owner); + api.actions().register("removeButton", ActionButtonRemove.class, new ActionButtonRemove.Serializer(), owner); + api.actions().register("removePlaced", ActionPlacedItemRemove.class, new ActionPlacedItemRemove.Serializer(), owner); + api.actions().register("placeItem", ActionPlaceItem.class, new ActionPlaceItem.Serializer(), owner); + + api.actions().register("setVar", ActionVarSet.class, new ActionVarSet.Serializer(), owner); + api.actions().register("removeVar", ActionVarRem.class, new ActionVarRem.Serializer(), owner); + api.actions().register("incVar", ActionVarInc.class, new ActionVarInc.Serializer(), owner); + api.actions().register("decVar", ActionVarDec.class, new ActionVarDec.Serializer(), owner); + api.actions().register("mulVar", ActionVarMul.class, new ActionVarMul.Serializer(), owner); + api.actions().register("divVar", ActionVarDiv.class, new ActionVarDiv.Serializer(), owner); + + api.actions().register("setVarp", ActionVarpSet.class, new ActionVarpSet.Serializer(), owner); + api.actions().register("removeVarp", ActionVarpRem.class, new ActionVarpRem.Serializer(), owner); + api.actions().register("incVarp", ActionVarpInc.class, new ActionVarpInc.Serializer(), owner); + api.actions().register("decVarp", ActionVarpDec.class, new ActionVarpDec.Serializer(), owner); + api.actions().register("mulVarp", ActionVarpMul.class, new ActionVarpMul.Serializer(), owner); + api.actions().register("divVarp", ActionVarpDiv.class, new ActionVarpDiv.Serializer(), owner); + + api.actions().register("print", ActionLog.class, new ActionLog.Serializer(), owner); + } +} diff --git a/plugin/src/main/java/ru/abstractmenus/core/CoreActivatorsBundle.java b/plugin/src/main/java/ru/abstractmenus/core/CoreActivatorsBundle.java new file mode 100644 index 0000000..728eafc --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/core/CoreActivatorsBundle.java @@ -0,0 +1,55 @@ +package ru.abstractmenus.core; + +import ru.abstractmenus.AbstractMenus; +import ru.abstractmenus.api.AbstractMenusApi; +import ru.abstractmenus.api.MenuExtension; +import ru.abstractmenus.data.activators.OpenButton; +import ru.abstractmenus.data.activators.OpenChat; +import ru.abstractmenus.data.activators.OpenChatContains; +import ru.abstractmenus.data.activators.OpenClickBlock; +import ru.abstractmenus.data.activators.OpenClickBlockType; +import ru.abstractmenus.data.activators.OpenClickEntity; +import ru.abstractmenus.data.activators.OpenClickItem; +import ru.abstractmenus.data.activators.OpenClickNPC; +import ru.abstractmenus.data.activators.OpenCommand; +import ru.abstractmenus.data.activators.OpenJoin; +import ru.abstractmenus.data.activators.OpenLever; +import ru.abstractmenus.data.activators.OpenPlate; +import ru.abstractmenus.data.activators.OpenRegionEnter; +import ru.abstractmenus.data.activators.OpenRegionLeave; +import ru.abstractmenus.data.activators.OpenShiftClickEntity; +import ru.abstractmenus.data.activators.OpenSign; +import ru.abstractmenus.data.activators.OpenSwapItems; + +/** + * Core activator registrations. Mirrors {@code Activators.init()} before + * the SPI refactor. + */ +final class CoreActivatorsBundle { + + void register(AbstractMenusApi api, MenuExtension owner) { + api.activators().register("command", OpenCommand.class, new OpenCommand.Serializer(), owner); + api.activators().register("chat", OpenChat.class, new OpenChat.Serializer(), owner); + api.activators().register("containsChat", OpenChatContains.class, new OpenChatContains.Serializer(), owner); + api.activators().register("join", OpenJoin.class, new OpenJoin.Serializer(), owner); + api.activators().register("clickEntity", OpenClickEntity.class, new OpenClickEntity.Serializer(), owner); + api.activators().register("shiftClickEntity", OpenShiftClickEntity.class, new OpenShiftClickEntity.Serializer(), owner); + api.activators().register("clickItem", OpenClickItem.class, new OpenClickItem.Serializer(), owner); + api.activators().register("button", OpenButton.class, new OpenButton.Serializer(), owner); + api.activators().register("lever", OpenLever.class, new OpenLever.Serializer(), owner); + api.activators().register("plate", OpenPlate.class, new OpenPlate.Serializer(), owner); + api.activators().register("table", OpenSign.class, new OpenSign.Serializer(), owner); + api.activators().register("clickBlock", OpenClickBlock.class, new OpenClickBlock.Serializer(), owner); + api.activators().register("clickBlockType", OpenClickBlockType.class, new OpenClickBlockType.Serializer(), owner); + api.activators().register("swapItems", OpenSwapItems.class, new OpenSwapItems.Serializer(), owner); + + if (AbstractMenus.checkDependency("Citizens")) { + api.activators().register("clickNPC", OpenClickNPC.class, new OpenClickNPC.Serializer(), owner); + } + + if (AbstractMenus.checkDependency("WorldGuard")) { + api.activators().register("regionJoin", OpenRegionEnter.class, new OpenRegionEnter.Serializer(), owner); + api.activators().register("regionLeave", OpenRegionLeave.class, new OpenRegionLeave.Serializer(), owner); + } + } +} diff --git a/plugin/src/main/java/ru/abstractmenus/core/CoreCatalogsBundle.java b/plugin/src/main/java/ru/abstractmenus/core/CoreCatalogsBundle.java new file mode 100644 index 0000000..fb66d36 --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/core/CoreCatalogsBundle.java @@ -0,0 +1,26 @@ +package ru.abstractmenus.core; + +import ru.abstractmenus.api.AbstractMenusApi; +import ru.abstractmenus.api.MenuExtension; +import ru.abstractmenus.data.catalogs.IteratorCatalog; +import ru.abstractmenus.data.catalogs.PlayerCatalog; +import ru.abstractmenus.data.catalogs.EntityCatalog; +import ru.abstractmenus.data.catalogs.WorldCatalog; +import ru.abstractmenus.data.catalogs.ServerCatalog; +import ru.abstractmenus.data.catalogs.SliceCatalog; + +/** + * Core catalog registrations. Mirrors {@code Catalogs.init()} before the + * SPI refactor. + */ +final class CoreCatalogsBundle { + + void register(AbstractMenusApi api, MenuExtension owner) { + api.catalogs().register("iterator", IteratorCatalog.class, new IteratorCatalog.Serializer(), owner); + api.catalogs().register("players", PlayerCatalog.class, new PlayerCatalog.Serializer(), owner); + api.catalogs().register("entities", EntityCatalog.class, new EntityCatalog.Serializer(), owner); + api.catalogs().register("worlds", WorldCatalog.class, new WorldCatalog.Serializer(), owner); + api.catalogs().register("bungee_servers", ServerCatalog.class, new ServerCatalog.Serializer(), owner); + api.catalogs().register("slice", SliceCatalog.class, new SliceCatalog.Serializer(), owner); + } +} diff --git a/plugin/src/main/java/ru/abstractmenus/core/CoreExtension.java b/plugin/src/main/java/ru/abstractmenus/core/CoreExtension.java new file mode 100644 index 0000000..8d5649d --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/core/CoreExtension.java @@ -0,0 +1,38 @@ +package ru.abstractmenus.core; + +import ru.abstractmenus.api.AbstractMenusApi; +import ru.abstractmenus.api.MenuExtension; + +/** + * The built-in "extension" that registers every core action, rule, item + * property, activator and catalog. Lives inside the plugin itself (not + * loaded via the addon classloader) and acts as the reference example of + * how external addons should structure their registration. + * + *

The five bundles keep registration grouped by surface area for + * readability. Each is a pure function of {@code api} and the owning + * {@code CoreExtension} instance — no static state. + */ +public final class CoreExtension implements MenuExtension { + + @Override + public String name() { + return "AbstractMenus-Core"; + } + + @Override + public String version() { + // Populated from plugin version at runtime via api.apiVersion(). + return null; + } + + @Override + public void onEnable(AbstractMenusApi api) { + new CoreActionsBundle().register(api, this); + new CoreRulesBundle().register(api, this); + new CoreItemPropsBundle().register(api, this); + new CoreActivatorsBundle().register(api, this); + new CoreCatalogsBundle().register(api, this); + new CoreProvidersBundle().register(api, this); + } +} diff --git a/plugin/src/main/java/ru/abstractmenus/core/CoreItemPropertyKeys.java b/plugin/src/main/java/ru/abstractmenus/core/CoreItemPropertyKeys.java new file mode 100644 index 0000000..205be89 --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/core/CoreItemPropertyKeys.java @@ -0,0 +1,20 @@ +package ru.abstractmenus.core; + +/** + * HOCON-visible keys for core item properties. Centralised so that the + * registration site ({@link CoreItemPropsBundle}) and consumers elsewhere + * in the plugin reference the same symbol rather than duplicating string + * literals. + * + *

Only those keys that are referenced by name outside + * {@link CoreItemPropsBundle} need a constant here. The rest stay as + * literals at their single point of use. + */ +public final class CoreItemPropertyKeys { + + private CoreItemPropertyKeys() { + } + + /** The {@code bindings} property — referenced by {@code actionClear}. */ + public static final String BINDINGS = "bindings"; +} diff --git a/plugin/src/main/java/ru/abstractmenus/core/CoreItemPropsBundle.java b/plugin/src/main/java/ru/abstractmenus/core/CoreItemPropsBundle.java new file mode 100644 index 0000000..8c4a65a --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/core/CoreItemPropsBundle.java @@ -0,0 +1,80 @@ +package ru.abstractmenus.core; + +import ru.abstractmenus.api.AbstractMenusApi; +import ru.abstractmenus.api.MenuExtension; +import ru.abstractmenus.data.properties.PropMaterial; +import ru.abstractmenus.data.properties.PropTexture; +import ru.abstractmenus.data.properties.PropSkullOwner; +import ru.abstractmenus.data.properties.PropHDB; +import ru.abstractmenus.data.properties.PropMmoItem; +import ru.abstractmenus.data.properties.PropItemsAdder; +import ru.abstractmenus.data.properties.PropOraxen; +import ru.abstractmenus.data.properties.PropEquipItem; +import ru.abstractmenus.data.properties.PropSerialized; +import ru.abstractmenus.data.properties.PropName; +import ru.abstractmenus.data.properties.PropData; +import ru.abstractmenus.data.properties.PropCount; +import ru.abstractmenus.data.properties.PropLore; +import ru.abstractmenus.data.properties.PropGlow; +import ru.abstractmenus.data.properties.PropEnchantments; +import ru.abstractmenus.data.properties.PropColor; +import ru.abstractmenus.data.properties.PropFlags; +import ru.abstractmenus.data.properties.PropUnbreakable; +import ru.abstractmenus.data.properties.PropPotionData; +import ru.abstractmenus.data.properties.PropFireworkData; +import ru.abstractmenus.data.properties.PropAttributeModifier; +import ru.abstractmenus.data.properties.PropBookData; +import ru.abstractmenus.data.properties.PropBannerData; +import ru.abstractmenus.data.properties.PropShieldData; +import ru.abstractmenus.data.properties.PropModel; +import ru.abstractmenus.data.properties.PropEnchantStore; +import ru.abstractmenus.data.properties.PropKnowledgeBook; +import ru.abstractmenus.data.properties.PropDamage; +import ru.abstractmenus.data.properties.PropNbt; +import ru.abstractmenus.data.properties.PropBindings; +import ru.abstractmenus.data.properties.PropNameLight; +import ru.abstractmenus.data.properties.PropLoreLight; + +/** + * Core item property registrations. Mirrors {@code ItemProps.init()} before + * the SPI refactor. + */ +final class CoreItemPropsBundle { + + void register(AbstractMenusApi api, MenuExtension owner) { + api.itemProperties().register("material", PropMaterial.class, new PropMaterial.Serializer(), owner); + api.itemProperties().register("texture", PropTexture.class, new PropTexture.Serializer(), owner); + api.itemProperties().register("skullOwner", PropSkullOwner.class, new PropSkullOwner.Serializer(), owner); + api.itemProperties().register("hdb", PropHDB.class, new PropHDB.Serializer(), owner); + api.itemProperties().register("mmoitem", PropMmoItem.class, new PropMmoItem.Serializer(), owner); + api.itemProperties().register("itemsAdder", PropItemsAdder.class, new PropItemsAdder.Serializer(), owner); + api.itemProperties().register("oraxen", PropOraxen.class, new PropOraxen.Serializer(), owner); + api.itemProperties().register("equipItem", PropEquipItem.class, new PropEquipItem.Serializer(), owner); + api.itemProperties().register("serialized", PropSerialized.class, new PropSerialized.Serializer(), owner); + + api.itemProperties().register("name", PropName.class, new PropName.Serializer(), owner); + api.itemProperties().register("data", PropData.class, new PropData.Serializer(), owner); + api.itemProperties().register("count", PropCount.class, new PropCount.Serializer(), owner); + api.itemProperties().register("lore", PropLore.class, new PropLore.Serializer(), owner); + api.itemProperties().register("glow", PropGlow.class, new PropGlow.Serializer(), owner); + api.itemProperties().register("enchantments", PropEnchantments.class, new PropEnchantments.Serializer(), owner); + api.itemProperties().register("color", PropColor.class, new PropColor.Serializer(), owner); + api.itemProperties().register("flags", PropFlags.class, new PropFlags.Serializer(), owner); + api.itemProperties().register("unbreakable", PropUnbreakable.class, new PropUnbreakable.Serializer(), owner); + api.itemProperties().register("potionData", PropPotionData.class, new PropPotionData.Serializer(), owner); + api.itemProperties().register("fireworkData", PropFireworkData.class, new PropFireworkData.Serializer(), owner); + api.itemProperties().register("attributeModifier", PropAttributeModifier.class, new PropAttributeModifier.Serializer(), owner); + api.itemProperties().register("bookData", PropBookData.class, new PropBookData.Serializer(), owner); + api.itemProperties().register("bannerData", PropBannerData.class, new PropBannerData.Serializer(), owner); + api.itemProperties().register("shieldData", PropShieldData.class, new PropShieldData.Serializer(), owner); + api.itemProperties().register("model", PropModel.class, new PropModel.Serializer(), owner); + api.itemProperties().register("enchantStore", PropEnchantStore.class, new PropEnchantStore.Serializer(), owner); + api.itemProperties().register("recipes", PropKnowledgeBook.class, new PropKnowledgeBook.Serializer(), owner); + api.itemProperties().register("damage", PropDamage.class, new PropDamage.Serializer(), owner); + api.itemProperties().register("nbt", PropNbt.class, new PropNbt.Serializer(), owner); + api.itemProperties().register(CoreItemPropertyKeys.BINDINGS, PropBindings.class, new PropBindings.Serializer(), owner); + + api.itemProperties().register("nameLight", PropNameLight.class, new PropNameLight.Serializer(), owner); + api.itemProperties().register("loreLight", PropLoreLight.class, new PropLoreLight.Serializer(), owner); + } +} diff --git a/plugin/src/main/java/ru/abstractmenus/core/CoreProvidersBundle.java b/plugin/src/main/java/ru/abstractmenus/core/CoreProvidersBundle.java new file mode 100644 index 0000000..aa63fc6 --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/core/CoreProvidersBundle.java @@ -0,0 +1,117 @@ +package ru.abstractmenus.core; + +import net.luckperms.api.LuckPerms; +import net.milkbowl.vault.economy.Economy; +import org.bukkit.Bukkit; +import org.bukkit.plugin.RegisteredServiceProvider; +import ru.abstractmenus.AbstractMenus; +import ru.abstractmenus.api.AbstractMenusApi; +import ru.abstractmenus.api.Logger; +import ru.abstractmenus.api.MenuExtension; +import ru.abstractmenus.handlers.EconomyVaultHandler; +import ru.abstractmenus.handlers.LevelDefaultHandler; +import ru.abstractmenus.handlers.LuckPermsHandler; +import ru.abstractmenus.handlers.PermissionDefaultHandler; +import ru.abstractmenus.handlers.SkinsRestorerHandler; +import ru.abstractmenus.handlers.placeholder.PlaceholderCustomHandler; +import ru.abstractmenus.handlers.placeholder.PlaceholderDefaultHandler; + +/** + * Registers built-in handler providers (economy, permissions, levels, + * placeholders, skins). Mirrors the old {@code AbstractMenus.registerProviders} + * behaviour — same conditions, same priorities — but through the + * new {@link ru.abstractmenus.api.ProviderRegistry} SPI. + * + *

All core providers register at priority 50 so addons can override by + * registering at priority > 50. + */ +final class CoreProvidersBundle { + + private static final int CORE_PRIORITY = 50; + + void register(AbstractMenusApi api, MenuExtension owner) { + AbstractMenus plugin = AbstractMenus.instance(); + + // ---- Economy -------------------------------------------------------- + if (AbstractMenus.checkDependency("Vault")) { + RegisteredServiceProvider economyProvider = + Bukkit.getServicesManager().getRegistration(Economy.class); + + if (economyProvider != null) { + api.providers().registerEconomy( + "vault", + new EconomyVaultHandler(economyProvider.getProvider()), + CORE_PRIORITY, + owner); + Logger.info("Registered core economy provider: vault"); + } else { + Logger.warning("Economy plugin doesn't installed"); + } + } else { + Logger.warning("Vault doesn't installed. Economy actions and rules won't work"); + } + + // ---- Permissions ---------------------------------------------------- + if (AbstractMenus.checkDependency("LuckPerms")) { + RegisteredServiceProvider provider = + Bukkit.getServicesManager().getRegistration(LuckPerms.class); + + if (provider != null) { + api.providers().registerPermissions( + "luckperms", + new LuckPermsHandler(provider.getProvider()), + CORE_PRIORITY, + owner); + Logger.info("Using LuckPerms"); + } else { + Logger.severe("Cannot find registered LuckPerms service"); + } + } else { + api.providers().registerPermissions( + "default", + new PermissionDefaultHandler(plugin), + CORE_PRIORITY, + owner); + Logger.info("Using bundled temporary permissions manager"); + Logger.warning("LuckPerms doesn't installed. After reload all assigned permissions will be removed"); + } + + // ---- Levels --------------------------------------------------------- + api.providers().registerLevels( + "default", + new LevelDefaultHandler(), + CORE_PRIORITY, + owner); + + // ---- Placeholders --------------------------------------------------- + if (AbstractMenus.checkDependency("PlaceholderAPI")) { + PlaceholderCustomHandler papiHandler = new PlaceholderCustomHandler(); + api.providers().registerPlaceholders( + "placeholderapi", + papiHandler, + CORE_PRIORITY, + owner); + papiHandler.registerAll(); + Logger.info("Using PlaceholderAPI"); + } else { + PlaceholderDefaultHandler defaultHandler = new PlaceholderDefaultHandler(); + api.providers().registerPlaceholders( + "default", + defaultHandler, + CORE_PRIORITY, + owner); + defaultHandler.registerAll(); + Logger.info("Using bundled placeholders"); + } + + // ---- Skins ---------------------------------------------------------- + if (AbstractMenus.checkDependency("SkinsRestorer")) { + api.providers().registerSkins( + "skinsrestorer", + new SkinsRestorerHandler(plugin.isProxyMode, plugin), + CORE_PRIORITY, + owner); + Logger.info("Using SkinsRestorer as skins provider"); + } + } +} diff --git a/plugin/src/main/java/ru/abstractmenus/core/CoreRulesBundle.java b/plugin/src/main/java/ru/abstractmenus/core/CoreRulesBundle.java new file mode 100644 index 0000000..91c3b64 --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/core/CoreRulesBundle.java @@ -0,0 +1,71 @@ +package ru.abstractmenus.core; + +import ru.abstractmenus.api.AbstractMenusApi; +import ru.abstractmenus.api.MenuExtension; +import ru.abstractmenus.data.rules.RuleChance; +import ru.abstractmenus.data.rules.RuleIf; +import ru.abstractmenus.data.rules.RuleFoodLevel; +import ru.abstractmenus.data.rules.RuleGameMode; +import ru.abstractmenus.data.rules.RuleGroup; +import ru.abstractmenus.data.rules.RuleHealth; +import ru.abstractmenus.data.rules.RuleInventoryItem; +import ru.abstractmenus.data.rules.RuleHeldItem; +import ru.abstractmenus.data.rules.RuleLevel; +import ru.abstractmenus.data.rules.RuleMoney; +import ru.abstractmenus.data.rules.RuleOnline; +import ru.abstractmenus.data.rules.RuleBungeeOnline; +import ru.abstractmenus.data.rules.RulePermission; +import ru.abstractmenus.data.rules.RuleWorld; +import ru.abstractmenus.data.rules.RuleXp; +import ru.abstractmenus.data.rules.RuleExistVar; +import ru.abstractmenus.data.rules.RuleExistVarp; +import ru.abstractmenus.data.rules.RuleFreeSlot; +import ru.abstractmenus.data.rules.RuleFreeSlotCount; +import ru.abstractmenus.data.rules.RuleRegion; +import ru.abstractmenus.data.rules.RuleJS; +import ru.abstractmenus.data.rules.RuleBungeeIsOnline; +import ru.abstractmenus.data.rules.RulePlayerIsOnline; +import ru.abstractmenus.data.rules.RulePlacedItem; +import ru.abstractmenus.data.rules.logical.RuleOneOf; +import ru.abstractmenus.data.rules.logical.RuleOr; +import ru.abstractmenus.data.rules.logical.RuleAnd; +import ru.abstractmenus.data.rules.logical.RulePlayerScope; + +/** + * Core rule registrations. Mirrors {@code MenuRules.init()} before the + * SPI refactor. + */ +final class CoreRulesBundle { + + void register(AbstractMenusApi api, MenuExtension owner) { + api.rules().register("chance", RuleChance.class, new RuleChance.Serializer(), owner); + api.rules().register("if", RuleIf.class, new RuleIf.Serializer(), owner); + api.rules().register("foodLevel", RuleFoodLevel.class, new RuleFoodLevel.Serializer(), owner); + api.rules().register("gamemode", RuleGameMode.class, new RuleGameMode.Serializer(), owner); + api.rules().register("group", RuleGroup.class, new RuleGroup.Serializer(), owner); + api.rules().register("health", RuleHealth.class, new RuleHealth.Serializer(), owner); + api.rules().register("inventoryItems", RuleInventoryItem.class, new RuleInventoryItem.Serializer(), owner); + api.rules().register("heldItem", RuleHeldItem.class, new RuleHeldItem.Serializer(), owner); + api.rules().register("level", RuleLevel.class, new RuleLevel.Serializer(), owner); + api.rules().register("money", RuleMoney.class, new RuleMoney.Serializer(), owner); + api.rules().register("online", RuleOnline.class, new RuleOnline.Serializer(), owner); + api.rules().register("bungeeOnline", RuleBungeeOnline.class, new RuleBungeeOnline.Serializer(), owner); + api.rules().register("permission", RulePermission.class, new RulePermission.Serializer(), owner); + api.rules().register("world", RuleWorld.class, new RuleWorld.Serializer(), owner); + api.rules().register("xp", RuleXp.class, new RuleXp.Serializer(), owner); + api.rules().register("existVar", RuleExistVar.class, new RuleExistVar.Serializer(), owner); + api.rules().register("existVarp", RuleExistVarp.class, new RuleExistVarp.Serializer(), owner); + api.rules().register("freeSlot", RuleFreeSlot.class, new RuleFreeSlot.Serializer(), owner); + api.rules().register("freeSlotCount", RuleFreeSlotCount.class, new RuleFreeSlotCount.Serializer(), owner); + api.rules().register("region", RuleRegion.class, new RuleRegion.Serializer(), owner); + api.rules().register("js", RuleJS.class, new RuleJS.Serializer(), owner); + api.rules().register("bungeeIsOnline", RuleBungeeIsOnline.class, new RuleBungeeIsOnline.Serializer(), owner); + api.rules().register("playerIsOnline", RulePlayerIsOnline.class, new RulePlayerIsOnline.Serializer(), owner); + api.rules().register("placedItem", RulePlacedItem.class, new RulePlacedItem.Serializer(), owner); + + api.rules().register("and", RuleAnd.class, new RuleAnd.Serializer(), owner); + api.rules().register("or", RuleOr.class, new RuleOr.Serializer(), owner); + api.rules().register("oneof", RuleOneOf.class, new RuleOneOf.Serializer(), owner); + api.rules().register("playerScope", RulePlayerScope.class, new RulePlayerScope.Serializer(), owner); + } +} diff --git a/src/main/java/ru/abstractmenus/data/Actions.java b/plugin/src/main/java/ru/abstractmenus/data/Actions.java similarity index 96% rename from src/main/java/ru/abstractmenus/data/Actions.java rename to plugin/src/main/java/ru/abstractmenus/data/Actions.java index 6491ea4..388a5b3 100644 --- a/src/main/java/ru/abstractmenus/data/Actions.java +++ b/plugin/src/main/java/ru/abstractmenus/data/Actions.java @@ -6,12 +6,12 @@ import ru.abstractmenus.hocon.api.serialize.NodeSerializeException; import ru.abstractmenus.hocon.api.serialize.NodeSerializer; import org.bukkit.entity.Player; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.Action; import ru.abstractmenus.api.Logger; import ru.abstractmenus.api.Rule; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Types; import ru.abstractmenus.data.rules.logical.RuleAnd; import ru.abstractmenus.util.StringUtil; @@ -122,7 +122,7 @@ public static void deserializeAction(String key, ConfigNode node, Actions action if (Actions.isReserved(key)) return; - Class type = Types.getActionType(key); + Class type = AbstractMenusApi.get().actions().get(key); if (type != null) { actions.add(node.getValue(type)); diff --git a/src/main/java/ru/abstractmenus/data/BannerData.java b/plugin/src/main/java/ru/abstractmenus/data/BannerData.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/BannerData.java rename to plugin/src/main/java/ru/abstractmenus/data/BannerData.java diff --git a/src/main/java/ru/abstractmenus/data/BookData.java b/plugin/src/main/java/ru/abstractmenus/data/BookData.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/BookData.java rename to plugin/src/main/java/ru/abstractmenus/data/BookData.java diff --git a/src/main/java/ru/abstractmenus/data/EntityData.java b/plugin/src/main/java/ru/abstractmenus/data/EntityData.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/EntityData.java rename to plugin/src/main/java/ru/abstractmenus/data/EntityData.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionBookOpen.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionBookOpen.java similarity index 91% rename from src/main/java/ru/abstractmenus/data/actions/ActionBookOpen.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionBookOpen.java index 7c5acbb..308f6aa 100644 --- a/src/main/java/ru/abstractmenus/data/actions/ActionBookOpen.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionBookOpen.java @@ -10,7 +10,7 @@ import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.Action; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; public class ActionBookOpen implements Action { @@ -23,7 +23,7 @@ private ActionBookOpen(BookData bookData) { @Override public void activate(Player player, Menu menu, Item clickedItem) { BookData data = bookData.clone(); - PlaceholderHandler handler = Handlers.getPlaceholderHandler(); + PlaceholderHandler handler = AbstractMenusApi.get().providers().placeholders(); data.setAuthor(handler.replace(player, bookData.getAuthor())); data.setTitle(handler.replace(player, bookData.getTitle())); diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionBroadcast.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionBroadcast.java similarity index 89% rename from src/main/java/ru/abstractmenus/data/actions/ActionBroadcast.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionBroadcast.java index 8d75ec8..c10e963 100644 --- a/src/main/java/ru/abstractmenus/data/actions/ActionBroadcast.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionBroadcast.java @@ -11,7 +11,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.text.Colors; import ru.abstractmenus.nms.actionbar.ActionBar; import ru.abstractmenus.nms.title.Title; @@ -70,7 +70,7 @@ private void setStay(TypeInt stay) { public void activate(Player player, Menu menu, Item clickedItem) { if (player != null) { if (chatMessages != null) { - List replaced = Handlers.getPlaceholderHandler().replace(player, chatMessages); + List replaced = AbstractMenusApi.get().providers().placeholders().replace(player, chatMessages); for (Player p : Bukkit.getOnlinePlayers()) { MiniMessageUtil.sendParsed(replaced, p); @@ -79,7 +79,7 @@ public void activate(Player player, Menu menu, Item clickedItem) { if (json != null) { BaseComponent[] component = ComponentSerializer.parse( - Handlers.getPlaceholderHandler().replace(player, json)); + AbstractMenusApi.get().providers().placeholders().replace(player, json)); if (component != null) { for (Player p : Bukkit.getOnlinePlayers()) @@ -88,7 +88,7 @@ public void activate(Player player, Menu menu, Item clickedItem) { } if (actionbar != null) { - String replaced = Handlers.getPlaceholderHandler().replace(player, actionbar); + String replaced = AbstractMenusApi.get().providers().placeholders().replace(player, actionbar); ActionBar bar = ActionBar.create(); for (Player p : Bukkit.getOnlinePlayers()) @@ -97,10 +97,10 @@ public void activate(Player player, Menu menu, Item clickedItem) { if (!this.title.isEmpty() || !this.subtitle.isEmpty()) { String title = MiniMessageUtil.parseToLegacy( - Handlers.getPlaceholderHandler().replace(player, this.title) + AbstractMenusApi.get().providers().placeholders().replace(player, this.title) ); String subtitle = MiniMessageUtil.parseToLegacy( - Handlers.getPlaceholderHandler().replace(player, this.subtitle) + AbstractMenusApi.get().providers().placeholders().replace(player, this.subtitle) ); Title t = new Title( title, subtitle, diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionBungeeConnect.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionBungeeConnect.java similarity index 86% rename from src/main/java/ru/abstractmenus/data/actions/ActionBungeeConnect.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionBungeeConnect.java index 3dfec52..290e919 100644 --- a/src/main/java/ru/abstractmenus/data/actions/ActionBungeeConnect.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionBungeeConnect.java @@ -6,7 +6,7 @@ import org.bukkit.entity.Player; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.services.BungeeManager; import ru.abstractmenus.api.Action; @@ -20,7 +20,7 @@ private ActionBungeeConnect(String serverName) { @Override public void activate(Player player, Menu menu, Item clickedItem) { - String replaced = Handlers.getPlaceholderHandler().replace(player, serverName); + String replaced = AbstractMenusApi.get().providers().placeholders().replace(player, serverName); BungeeManager.instance().sendPluginMessage(player, "Connect", replaced); } diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionButtonRemove.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionButtonRemove.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionButtonRemove.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionButtonRemove.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionButtonSet.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionButtonSet.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionButtonSet.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionButtonSet.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionCommand.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionCommand.java similarity index 93% rename from src/main/java/ru/abstractmenus/data/actions/ActionCommand.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionCommand.java index 426ca03..bedb95c 100644 --- a/src/main/java/ru/abstractmenus/data/actions/ActionCommand.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionCommand.java @@ -5,7 +5,7 @@ import org.bukkit.entity.Player; import ru.abstractmenus.AbstractMenus; import ru.abstractmenus.api.Action; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.hocon.api.ConfigNode; @@ -37,7 +37,7 @@ private void setIgnorePlaceholder(boolean isIgnorePlaceholder) { public void activate(Player player, Menu menu, Item clickedItem) { for (String command : playerCommands) { if (command != null) { - String resultCommand = isIgnorePlaceholder ? command : Handlers.getPlaceholderHandler().replace(player, command); + String resultCommand = isIgnorePlaceholder ? command : AbstractMenusApi.get().providers().placeholders().replace(player, command); player.performCommand(resultCommand); } } @@ -46,7 +46,7 @@ public void activate(Player player, Menu menu, Item clickedItem) { Bukkit.getServer().getGlobalRegionScheduler().execute(AbstractMenus.instance(), () -> { for (String command : consoleCommands) { if (command != null) { - String resultCommand = isIgnorePlaceholder ? command : Handlers.getPlaceholderHandler().replace(player, command); + String resultCommand = isIgnorePlaceholder ? command : AbstractMenusApi.get().providers().placeholders().replace(player, command); Bukkit.dispatchCommand(Bukkit.getConsoleSender(), resultCommand); } } diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionFoodLevelSet.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionFoodLevelSet.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionFoodLevelSet.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionFoodLevelSet.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionGameModeSet.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionGameModeSet.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionGameModeSet.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionGameModeSet.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionGroupAdd.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionGroupAdd.java similarity index 77% rename from src/main/java/ru/abstractmenus/data/actions/ActionGroupAdd.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionGroupAdd.java index 9ea7333..464a29f 100644 --- a/src/main/java/ru/abstractmenus/data/actions/ActionGroupAdd.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionGroupAdd.java @@ -7,7 +7,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; public class ActionGroupAdd implements Action { @@ -19,8 +19,8 @@ private ActionGroupAdd(String group) { @Override public void activate(Player player, Menu menu, Item clickedItem) { - String group = Handlers.getPlaceholderHandler().replace(player, this.group); - Handlers.getPermissionsHandler().addGroup(player, group); + String group = AbstractMenusApi.get().providers().placeholders().replace(player, this.group); + AbstractMenusApi.get().providers().permissions().addGroup(player, group); } public static class Serializer implements NodeSerializer { diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionGroupRemove.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionGroupRemove.java similarity index 77% rename from src/main/java/ru/abstractmenus/data/actions/ActionGroupRemove.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionGroupRemove.java index 1866e3f..2589caf 100644 --- a/src/main/java/ru/abstractmenus/data/actions/ActionGroupRemove.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionGroupRemove.java @@ -7,7 +7,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; public class ActionGroupRemove implements Action { @@ -19,8 +19,8 @@ private ActionGroupRemove(String group) { @Override public void activate(Player player, Menu menu, Item clickedItem) { - String groupName = Handlers.getPlaceholderHandler().replace(player, group); - Handlers.getPermissionsHandler().removeGroup(player, groupName); + String groupName = AbstractMenusApi.get().providers().placeholders().replace(player, group); + AbstractMenusApi.get().providers().permissions().removeGroup(player, groupName); } public static class Serializer implements NodeSerializer { diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionHealthSet.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionHealthSet.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionHealthSet.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionHealthSet.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionInputChat.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionInputChat.java similarity index 96% rename from src/main/java/ru/abstractmenus/data/actions/ActionInputChat.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionInputChat.java index 0cf633a..ff4a5d3 100644 --- a/src/main/java/ru/abstractmenus/data/actions/ActionInputChat.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionInputChat.java @@ -3,7 +3,7 @@ import lombok.Getter; import org.bukkit.entity.Player; import ru.abstractmenus.api.Action; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.variables.Var; @@ -34,7 +34,7 @@ public ActionInputChat(String varName, boolean global, String cancelWord, @Override public void activate(Player player, Menu m, Item clickedItem) { - String name = Handlers.getPlaceholderHandler().replace(player, varName); + String name = AbstractMenusApi.get().providers().placeholders().replace(player, varName); InputAction action = new InputAction(player, name, global, cancelWord, onInput, onCancel); MenuManager.instance().saveInputAction(action); m.close(player); diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionInventoryClear.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionInventoryClear.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionInventoryClear.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionInventoryClear.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionItemAdd.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionItemAdd.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionItemAdd.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionItemAdd.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionItemClear.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionItemClear.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionItemClear.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionItemClear.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionItemRefresh.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionItemRefresh.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionItemRefresh.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionItemRefresh.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionItemRemove.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionItemRemove.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionItemRemove.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionItemRemove.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionLevelGive.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionLevelGive.java similarity index 86% rename from src/main/java/ru/abstractmenus/data/actions/ActionLevelGive.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionLevelGive.java index c4fb509..1a66c68 100644 --- a/src/main/java/ru/abstractmenus/data/actions/ActionLevelGive.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionLevelGive.java @@ -8,7 +8,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; public class ActionLevelGive implements Action { @@ -20,7 +20,7 @@ private ActionLevelGive(TypeInt level) { @Override public void activate(Player player, Menu menu, Item clickedItem) { - Handlers.getLevelHandler().giveLevel(player, level.getInt(player, menu)); + AbstractMenusApi.get().providers().levels().giveLevel(player, level.getInt(player, menu)); } public static class Serializer implements NodeSerializer { diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionLevelTake.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionLevelTake.java similarity index 86% rename from src/main/java/ru/abstractmenus/data/actions/ActionLevelTake.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionLevelTake.java index f2c03fd..4cc2281 100644 --- a/src/main/java/ru/abstractmenus/data/actions/ActionLevelTake.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionLevelTake.java @@ -9,7 +9,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; public class ActionLevelTake implements Action { @@ -21,7 +21,7 @@ private ActionLevelTake(TypeInt level) { @Override public void activate(Player player, Menu menu, Item clickedItem) { - Handlers.getLevelHandler().takeLevel(player, level.getInt(player, menu)); + AbstractMenusApi.get().providers().levels().takeLevel(player, level.getInt(player, menu)); } public static class Serializer implements NodeSerializer { diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionLog.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionLog.java similarity index 86% rename from src/main/java/ru/abstractmenus/data/actions/ActionLog.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionLog.java index 53a7a3d..7ad1bcb 100644 --- a/src/main/java/ru/abstractmenus/data/actions/ActionLog.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionLog.java @@ -2,7 +2,7 @@ import org.bukkit.entity.Player; import ru.abstractmenus.api.Action; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.Logger; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; @@ -20,7 +20,7 @@ private ActionLog(String message) { @Override public void activate(Player player, Menu menu, Item clickedItem) { - Logger.info(Handlers.getPlaceholderHandler().replace(player, message)); + Logger.info(AbstractMenusApi.get().providers().placeholders().replace(player, message)); } public static class Serializer implements NodeSerializer { diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionLuckPermsMetaRemove.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionLuckPermsMetaRemove.java similarity index 88% rename from src/main/java/ru/abstractmenus/data/actions/ActionLuckPermsMetaRemove.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionLuckPermsMetaRemove.java index c27a3a2..88a1260 100644 --- a/src/main/java/ru/abstractmenus/data/actions/ActionLuckPermsMetaRemove.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionLuckPermsMetaRemove.java @@ -4,7 +4,7 @@ import lombok.RequiredArgsConstructor; import org.bukkit.entity.Player; import ru.abstractmenus.api.Action; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.handlers.LuckPermsHandler; @@ -22,7 +22,7 @@ public class ActionLuckPermsMetaRemove implements Action { @Override public void activate(Player player, Menu menu, Item clickedItem) { metaList.forEach(metaKey -> { - if (Handlers.getPermissionsHandler() instanceof LuckPermsHandler handler) { + if (AbstractMenusApi.get().providers().permissions() instanceof LuckPermsHandler handler) { handler.removeMeta(player, metaKey); } }); diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionLuckPermsMetaSet.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionLuckPermsMetaSet.java similarity index 86% rename from src/main/java/ru/abstractmenus/data/actions/ActionLuckPermsMetaSet.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionLuckPermsMetaSet.java index 39626f9..377a474 100644 --- a/src/main/java/ru/abstractmenus/data/actions/ActionLuckPermsMetaSet.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionLuckPermsMetaSet.java @@ -4,7 +4,7 @@ import lombok.Setter; import org.bukkit.entity.Player; import ru.abstractmenus.api.Action; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.data.properties.PropLPMeta; @@ -24,8 +24,8 @@ public class ActionLuckPermsMetaSet implements Action { @Override public void activate(Player player, Menu menu, Item clickedItem) { metaList.forEach(meta -> { - String replacedValue = isIgnorePlaceholder ? meta.getValue() : Handlers.getPlaceholderHandler().replace(player, meta.getValue()); - if (Handlers.getPermissionsHandler() instanceof LuckPermsHandler handler) { + String replacedValue = isIgnorePlaceholder ? meta.getValue() : AbstractMenusApi.get().providers().placeholders().replace(player, meta.getValue()); + if (AbstractMenusApi.get().providers().permissions() instanceof LuckPermsHandler handler) { handler.addMeta(player, meta.getKey(), replacedValue); } }); diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionMenuClose.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMenuClose.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionMenuClose.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionMenuClose.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionMenuOpen.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMenuOpen.java similarity index 88% rename from src/main/java/ru/abstractmenus/data/actions/ActionMenuOpen.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionMenuOpen.java index e23d80f..aa39bbd 100644 --- a/src/main/java/ru/abstractmenus/data/actions/ActionMenuOpen.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMenuOpen.java @@ -8,7 +8,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.services.MenuManager; public class ActionMenuOpen implements Action { @@ -21,7 +21,7 @@ private ActionMenuOpen(String menu) { @Override public void activate(Player player, Menu menu, Item clickedItem) { - String name = Handlers.getPlaceholderHandler().replace(player, this.menu); + String name = AbstractMenusApi.get().providers().placeholders().replace(player, this.menu); Menu menuToOpen = MenuManager.instance().getMenu(name); if (menuToOpen != null) diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionMenuOpenCtx.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMenuOpenCtx.java similarity index 91% rename from src/main/java/ru/abstractmenus/data/actions/ActionMenuOpenCtx.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionMenuOpenCtx.java index 3b1958a..f332513 100644 --- a/src/main/java/ru/abstractmenus/data/actions/ActionMenuOpenCtx.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMenuOpenCtx.java @@ -3,7 +3,7 @@ import org.bukkit.entity.Player; import ru.abstractmenus.api.Action; import ru.abstractmenus.api.Activator; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.Logger; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; @@ -22,7 +22,7 @@ private ActionMenuOpenCtx(String menu) { @Override public void activate(Player player, Menu menu, Item clickedItem) { - String name = Handlers.getPlaceholderHandler().replace(player, this.menu); + String name = AbstractMenusApi.get().providers().placeholders().replace(player, this.menu); Menu menuToOpen = MenuManager.instance().getMenu(name); if (menuToOpen != null) { diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionMenuRefresh.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMenuRefresh.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionMenuRefresh.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionMenuRefresh.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionMessage.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMessage.java similarity index 88% rename from src/main/java/ru/abstractmenus/data/actions/ActionMessage.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionMessage.java index 470ca6f..dddf207 100644 --- a/src/main/java/ru/abstractmenus/data/actions/ActionMessage.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMessage.java @@ -10,7 +10,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.nms.actionbar.ActionBar; import ru.abstractmenus.nms.title.Title; import ru.abstractmenus.api.text.Colors; @@ -69,29 +69,29 @@ private void setStay(TypeInt stay) { public void activate(Player player, Menu menu, Item clickedItem) { if (player != null) { if (chatMessages != null) { - List replaced = Handlers.getPlaceholderHandler().replace(player, chatMessages); + List replaced = AbstractMenusApi.get().providers().placeholders().replace(player, chatMessages); MiniMessageUtil.sendParsed(replaced, player); } if (json != null) { BaseComponent[] component = ComponentSerializer.parse( - Handlers.getPlaceholderHandler().replace(player, json)); + AbstractMenusApi.get().providers().placeholders().replace(player, json)); if (component != null) player.spigot().sendMessage(component); } if (actionbar != null) { - String replaced = Handlers.getPlaceholderHandler().replace(player, actionbar); + String replaced = AbstractMenusApi.get().providers().placeholders().replace(player, actionbar); ActionBar.create().send(player, MiniMessageUtil.parseToLegacy(replaced)); } if (!this.title.isEmpty() || !this.subtitle.isEmpty()) { String title = MiniMessageUtil.parseToLegacy( - Handlers.getPlaceholderHandler().replace(player, this.title) + AbstractMenusApi.get().providers().placeholders().replace(player, this.title) ); String subtitle = MiniMessageUtil.parseToLegacy( - Handlers.getPlaceholderHandler().replace(player, this.subtitle) + AbstractMenusApi.get().providers().placeholders().replace(player, this.subtitle) ); new Title( diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionMiniMessage.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMiniMessage.java similarity index 87% rename from src/main/java/ru/abstractmenus/data/actions/ActionMiniMessage.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionMiniMessage.java index 15cacb1..5cfbc30 100644 --- a/src/main/java/ru/abstractmenus/data/actions/ActionMiniMessage.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMiniMessage.java @@ -7,7 +7,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.util.MiniMessageUtil; import java.util.Collections; @@ -22,7 +22,7 @@ private ActionMiniMessage(String message) { @Override public void activate(Player player, Menu menu, Item clickedItem) { - String replaced = Handlers.getPlaceholderHandler().replace(player, message); + String replaced = AbstractMenusApi.get().providers().placeholders().replace(player, message); MiniMessageUtil.sendParsed(Collections.singletonList(replaced), player); } diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMoneyGive.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMoneyGive.java new file mode 100644 index 0000000..6657241 --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMoneyGive.java @@ -0,0 +1,67 @@ +package ru.abstractmenus.data.actions; + + +import ru.abstractmenus.datatype.TypeDouble; +import ru.abstractmenus.hocon.api.ConfigNode; +import ru.abstractmenus.hocon.api.serialize.NodeSerializeException; +import ru.abstractmenus.hocon.api.serialize.NodeSerializer; +import org.bukkit.entity.Player; +import ru.abstractmenus.api.Action; +import ru.abstractmenus.api.handler.EconomyHandler; +import ru.abstractmenus.api.inventory.Menu; +import ru.abstractmenus.api.inventory.Item; +import ru.abstractmenus.api.AbstractMenusApi; + +public class ActionMoneyGive implements Action { + + private final TypeDouble money; + private final String provider; + + private ActionMoneyGive(TypeDouble money, String provider) { + this.money = money; + this.provider = provider; + } + + @Override + public void activate(Player player, Menu menu, Item clickedItem) { + EconomyHandler eco = provider != null + ? AbstractMenusApi.get().providers().economy(provider) + : AbstractMenusApi.get().providers().economy(); + if (eco == null) { + return; + } + eco.giveBalance(player, money.getDouble(player, menu)); + } + + public static class Serializer implements NodeSerializer { + + @Override + public ActionMoneyGive deserialize(Class type, ConfigNode node) throws NodeSerializeException { + TypeDouble money; + String provider = null; + + if (node.isMap()) { + money = node.node("amount").getValue(TypeDouble.class); + provider = node.node("provider").getString(null); + } else { + money = node.getValue(TypeDouble.class); + } + + if (provider != null) { + if (!AbstractMenusApi.get().providers().hasEconomy(provider)) { + StringBuilder known = new StringBuilder(); + for (EconomyHandler h : AbstractMenusApi.get().providers().allEconomy()) { + if (known.length() > 0) known.append(", "); + known.append(h.getClass().getSimpleName()); + } + throw new NodeSerializeException(node, + "Unknown economy provider '" + provider + "'. Registered: [" + + known + "]. Omit the 'provider' field for default selection."); + } + } + + return new ActionMoneyGive(money, provider); + } + + } +} diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMoneyTake.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMoneyTake.java new file mode 100644 index 0000000..757c2fe --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMoneyTake.java @@ -0,0 +1,67 @@ +package ru.abstractmenus.data.actions; + + +import ru.abstractmenus.datatype.TypeDouble; +import ru.abstractmenus.hocon.api.ConfigNode; +import ru.abstractmenus.hocon.api.serialize.NodeSerializeException; +import ru.abstractmenus.hocon.api.serialize.NodeSerializer; +import org.bukkit.entity.Player; +import ru.abstractmenus.api.Action; +import ru.abstractmenus.api.handler.EconomyHandler; +import ru.abstractmenus.api.inventory.Menu; +import ru.abstractmenus.api.inventory.Item; +import ru.abstractmenus.api.AbstractMenusApi; + +public class ActionMoneyTake implements Action { + + private final TypeDouble money; + private final String provider; + + private ActionMoneyTake(TypeDouble money, String provider) { + this.money = money; + this.provider = provider; + } + + @Override + public void activate(Player player, Menu menu, Item clickedItem) { + EconomyHandler eco = provider != null + ? AbstractMenusApi.get().providers().economy(provider) + : AbstractMenusApi.get().providers().economy(); + if (eco == null) { + return; + } + eco.takeBalance(player, money.getDouble(player, menu)); + } + + public static class Serializer implements NodeSerializer { + + @Override + public ActionMoneyTake deserialize(Class type, ConfigNode node) throws NodeSerializeException { + TypeDouble money; + String provider = null; + + if (node.isMap()) { + money = node.node("amount").getValue(TypeDouble.class); + provider = node.node("provider").getString(null); + } else { + money = node.getValue(TypeDouble.class); + } + + if (provider != null) { + if (!AbstractMenusApi.get().providers().hasEconomy(provider)) { + StringBuilder known = new StringBuilder(); + for (EconomyHandler h : AbstractMenusApi.get().providers().allEconomy()) { + if (known.length() > 0) known.append(", "); + known.append(h.getClass().getSimpleName()); + } + throw new NodeSerializeException(node, + "Unknown economy provider '" + provider + "'. Registered: [" + + known + "]. Omit the 'provider' field for default selection."); + } + } + + return new ActionMoneyTake(money, provider); + } + + } +} diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionPageNext.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPageNext.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionPageNext.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionPageNext.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionPagePrev.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPagePrev.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionPagePrev.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionPagePrev.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionPermissionGive.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPermissionGive.java similarity index 83% rename from src/main/java/ru/abstractmenus/data/actions/ActionPermissionGive.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionPermissionGive.java index fb54cbf..f2e5667 100644 --- a/src/main/java/ru/abstractmenus/data/actions/ActionPermissionGive.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPermissionGive.java @@ -4,7 +4,7 @@ import lombok.Setter; import org.bukkit.entity.Player; import ru.abstractmenus.api.Action; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.hocon.api.ConfigNode; @@ -27,8 +27,8 @@ public void setIgnorePlaceholder(boolean ignorePlaceholder) { @Override public void activate(Player player, Menu menu, Item clickedItem) { permissions.forEach(perm -> { - String replaced = isIgnorePlaceholder ? perm : Handlers.getPlaceholderHandler().replace(player, perm); - Handlers.getPermissionsHandler().addPermission(player, replaced); + String replaced = isIgnorePlaceholder ? perm : AbstractMenusApi.get().providers().placeholders().replace(player, perm); + AbstractMenusApi.get().providers().permissions().addPermission(player, replaced); }); } diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionPermissionRemove.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPermissionRemove.java similarity index 81% rename from src/main/java/ru/abstractmenus/data/actions/ActionPermissionRemove.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionPermissionRemove.java index a7dfd23..4afe92b 100644 --- a/src/main/java/ru/abstractmenus/data/actions/ActionPermissionRemove.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPermissionRemove.java @@ -8,7 +8,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import java.util.List; @@ -23,8 +23,8 @@ private ActionPermissionRemove(List permissions) { @Override public void activate(Player player, Menu menu, Item clickedItem) { permissions.forEach(perm -> { - String replaced = Handlers.getPlaceholderHandler().replace(player, perm); - Handlers.getPermissionsHandler().removePermission(player, replaced); + String replaced = AbstractMenusApi.get().providers().placeholders().replace(player, perm); + AbstractMenusApi.get().providers().permissions().removePermission(player, replaced); }); } diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionPlaceItem.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPlaceItem.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionPlaceItem.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionPlaceItem.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionPlacedItemRemove.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPlacedItemRemove.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionPlacedItemRemove.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionPlacedItemRemove.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionPlayerChat.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPlayerChat.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionPlayerChat.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionPlayerChat.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionPotionEffect.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPotionEffect.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionPotionEffect.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionPotionEffect.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionPotionEffectRemove.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPotionEffectRemove.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionPotionEffectRemove.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionPotionEffectRemove.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionPropertyRemove.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPropertyRemove.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionPropertyRemove.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionPropertyRemove.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionPropertySet.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPropertySet.java similarity index 90% rename from src/main/java/ru/abstractmenus/data/actions/ActionPropertySet.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionPropertySet.java index c7f3501..9d57b82 100644 --- a/src/main/java/ru/abstractmenus/data/actions/ActionPropertySet.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPropertySet.java @@ -1,19 +1,19 @@ package ru.abstractmenus.data.actions; -import ru.abstractmenus.data.properties.ItemProps; import ru.abstractmenus.datatype.TypeSlot; import ru.abstractmenus.hocon.api.ConfigNode; import ru.abstractmenus.hocon.api.serialize.NodeSerializeException; import ru.abstractmenus.hocon.api.serialize.NodeSerializer; import org.bukkit.entity.Player; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.ItemProperty; import ru.abstractmenus.api.inventory.Menu; -import ru.abstractmenus.api.Types; import ru.abstractmenus.menu.item.InventoryItem; import ru.abstractmenus.api.inventory.slot.SlotIndex; import ru.abstractmenus.api.inventory.Slot; +import ru.abstractmenus.core.CoreItemPropertyKeys; import java.util.*; @@ -40,7 +40,7 @@ public void activate(Player player, Menu menu, Item clickedItem) { slot.getSlots(index -> { // Just modifying ItemStack from inventory won't work because remProperty breaks it Item item = menu.getItem(index); - item.removeProperty(ItemProps.BINDINGS); + item.removeProperty(CoreItemPropertyKeys.BINDINGS); item.setProperties(properties); menu.setItem(SlotIndex.of(index), item, player); }); @@ -56,7 +56,7 @@ public ActionPropertySet deserialize(Class type, ConfigNode node) throws NodeSer for (Map.Entry entry : children.entrySet()) { String key = entry.getKey(); - Class token = Types.getItemPropertyType(key); + Class token = AbstractMenusApi.get().itemProperties().get(key); if (token != null) { propertyMap.put(key, entry.getValue().getValue(token)); diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionRecipeAdd.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionRecipeAdd.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionRecipeAdd.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionRecipeAdd.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionSkinReset.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionSkinReset.java similarity index 89% rename from src/main/java/ru/abstractmenus/data/actions/ActionSkinReset.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionSkinReset.java index fb5d91d..ae71ab0 100644 --- a/src/main/java/ru/abstractmenus/data/actions/ActionSkinReset.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionSkinReset.java @@ -9,7 +9,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; public class ActionSkinReset implements Action { @@ -21,7 +21,7 @@ private ActionSkinReset(TypeBool reset) { public void activate(Player player, Menu menu, Item clickedItem) { if (reset.getBool(player, menu)) { - Handlers.getSkinHandler().resetSkin(player); + AbstractMenusApi.get().providers().skins().resetSkin(player); } } diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionSkinSet.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionSkinSet.java similarity index 78% rename from src/main/java/ru/abstractmenus/data/actions/ActionSkinSet.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionSkinSet.java index 8036a43..ef7631e 100644 --- a/src/main/java/ru/abstractmenus/data/actions/ActionSkinSet.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionSkinSet.java @@ -8,7 +8,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; public class ActionSkinSet implements Action { @@ -21,9 +21,9 @@ private ActionSkinSet(String texture, String signature) { } public void activate(Player player, Menu menu, Item clickedItem) { - Handlers.getSkinHandler().setSkin(player, - Handlers.getPlaceholderHandler().replace(player, texture), - Handlers.getPlaceholderHandler().replace(player, signature)); + AbstractMenusApi.get().providers().skins().setSkin(player, + AbstractMenusApi.get().providers().placeholders().replace(player, texture), + AbstractMenusApi.get().providers().placeholders().replace(player, signature)); } public static class Serializer implements NodeSerializer { diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionSound.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionSound.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionSound.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionSound.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionSoundCustom.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionSoundCustom.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionSoundCustom.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionSoundCustom.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionTeleport.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionTeleport.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionTeleport.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionTeleport.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionXpGive.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionXpGive.java similarity index 87% rename from src/main/java/ru/abstractmenus/data/actions/ActionXpGive.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionXpGive.java index 89c4b1e..7641dc6 100644 --- a/src/main/java/ru/abstractmenus/data/actions/ActionXpGive.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionXpGive.java @@ -9,7 +9,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; public class ActionXpGive implements Action { @@ -21,7 +21,7 @@ private ActionXpGive(TypeInt xp) { @Override public void activate(Player player, Menu menu, Item clickedItem) { - Handlers.getLevelHandler().giveXp(player, xp.getInt(player, menu)); + AbstractMenusApi.get().providers().levels().giveXp(player, xp.getInt(player, menu)); } public static class Serializer implements NodeSerializer { diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionXpTake.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionXpTake.java similarity index 87% rename from src/main/java/ru/abstractmenus/data/actions/ActionXpTake.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionXpTake.java index 956e983..a3a9a10 100644 --- a/src/main/java/ru/abstractmenus/data/actions/ActionXpTake.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionXpTake.java @@ -8,7 +8,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; public class ActionXpTake implements Action { @@ -20,7 +20,7 @@ private ActionXpTake(TypeInt xp) { @Override public void activate(Player player, Menu menu, Item clickedItem) { - Handlers.getLevelHandler().takeXp(player, xp.getInt(player, menu)); + AbstractMenusApi.get().providers().levels().takeXp(player, xp.getInt(player, menu)); } public static class Serializer implements NodeSerializer { diff --git a/src/main/java/ru/abstractmenus/data/actions/var/ActionVarDec.java b/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarDec.java similarity index 84% rename from src/main/java/ru/abstractmenus/data/actions/var/ActionVarDec.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarDec.java index 4789e8d..8fb3990 100644 --- a/src/main/java/ru/abstractmenus/data/actions/var/ActionVarDec.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarDec.java @@ -7,7 +7,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.variables.VarNumData; import ru.abstractmenus.variables.VariableManagerImpl; @@ -24,14 +24,14 @@ private ActionVarDec(List dataList) { public void activate(Player p, Menu menu, Item clickedItem) { for (VarNumData data : dataList) { - String varName = Handlers.getPlaceholderHandler().replace(p, data.getName()); + String varName = AbstractMenusApi.get().providers().placeholders().replace(p, data.getName()); double value = data.getValue().getDouble(p, menu); Function func = num -> num - value; if (data.getPlayer() == null) { VariableManagerImpl.instance().modifyNumericGlobal(varName, func); } else { - String playerName = Handlers.getPlaceholderHandler().replace(p, data.getPlayer()); + String playerName = AbstractMenusApi.get().providers().placeholders().replace(p, data.getPlayer()); VariableManagerImpl.instance().modifyNumericPersonal(playerName, varName, func); } } diff --git a/src/main/java/ru/abstractmenus/data/actions/var/ActionVarDiv.java b/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarDiv.java similarity index 86% rename from src/main/java/ru/abstractmenus/data/actions/var/ActionVarDiv.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarDiv.java index ce9496b..c4131f5 100644 --- a/src/main/java/ru/abstractmenus/data/actions/var/ActionVarDiv.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarDiv.java @@ -8,7 +8,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.variables.VarNumData; import ru.abstractmenus.variables.VariableManagerImpl; @@ -25,7 +25,7 @@ private ActionVarDiv(List dataList) { public void activate(Player p, Menu menu, Item clickedItem) { for (VarNumData data : dataList) { - String varName = Handlers.getPlaceholderHandler().replace(p, data.getName()); + String varName = AbstractMenusApi.get().providers().placeholders().replace(p, data.getName()); double value = data.getValue().getDouble(p, menu); if (value == 0) { @@ -38,7 +38,7 @@ public void activate(Player p, Menu menu, Item clickedItem) { if (data.getPlayer() == null) { VariableManagerImpl.instance().modifyNumericGlobal(varName, func); } else { - String playerName = Handlers.getPlaceholderHandler().replace(p, data.getPlayer()); + String playerName = AbstractMenusApi.get().providers().placeholders().replace(p, data.getPlayer()); VariableManagerImpl.instance().modifyNumericPersonal(playerName, varName, func); } } diff --git a/src/main/java/ru/abstractmenus/data/actions/var/ActionVarInc.java b/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarInc.java similarity index 84% rename from src/main/java/ru/abstractmenus/data/actions/var/ActionVarInc.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarInc.java index cc98032..363a635 100644 --- a/src/main/java/ru/abstractmenus/data/actions/var/ActionVarInc.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarInc.java @@ -7,7 +7,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.variables.VarNumData; import ru.abstractmenus.variables.VariableManagerImpl; @@ -24,14 +24,14 @@ private ActionVarInc(List dataList) { public void activate(Player p, Menu menu, Item clickedItem) { for (VarNumData data : dataList) { - String varName = Handlers.getPlaceholderHandler().replace(p, data.getName()); + String varName = AbstractMenusApi.get().providers().placeholders().replace(p, data.getName()); double value = data.getValue().getDouble(p, menu); Function func = num -> num + value; if (data.getPlayer() == null) { VariableManagerImpl.instance().modifyNumericGlobal(varName, func); } else { - String playerName = Handlers.getPlaceholderHandler().replace(p, data.getPlayer()); + String playerName = AbstractMenusApi.get().providers().placeholders().replace(p, data.getPlayer()); VariableManagerImpl.instance().modifyNumericPersonal(playerName, varName, func); } } diff --git a/src/main/java/ru/abstractmenus/data/actions/var/ActionVarMul.java b/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarMul.java similarity index 84% rename from src/main/java/ru/abstractmenus/data/actions/var/ActionVarMul.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarMul.java index 5b10c43..44f1c0b 100644 --- a/src/main/java/ru/abstractmenus/data/actions/var/ActionVarMul.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarMul.java @@ -7,7 +7,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.variables.VarNumData; import ru.abstractmenus.variables.VariableManagerImpl; @@ -24,14 +24,14 @@ private ActionVarMul(List dataList) { public void activate(Player p, Menu menu, Item clickedItem) { for (VarNumData data : dataList) { - String varName = Handlers.getPlaceholderHandler().replace(p, data.getName()); + String varName = AbstractMenusApi.get().providers().placeholders().replace(p, data.getName()); double value = data.getValue().getDouble(p, menu); Function func = num -> num * value; if (data.getPlayer() == null) { VariableManagerImpl.instance().modifyNumericGlobal(varName, func); } else { - String playerName = Handlers.getPlaceholderHandler().replace(p, data.getPlayer()); + String playerName = AbstractMenusApi.get().providers().placeholders().replace(p, data.getPlayer()); VariableManagerImpl.instance().modifyNumericPersonal(playerName, varName, func); } } diff --git a/src/main/java/ru/abstractmenus/data/actions/var/ActionVarRem.java b/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarRem.java similarity index 83% rename from src/main/java/ru/abstractmenus/data/actions/var/ActionVarRem.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarRem.java index 3a635fd..d881d35 100644 --- a/src/main/java/ru/abstractmenus/data/actions/var/ActionVarRem.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarRem.java @@ -7,7 +7,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.variables.VarData; import ru.abstractmenus.variables.VariableManagerImpl; @@ -23,12 +23,12 @@ private ActionVarRem(List dataList) { public void activate(Player p, Menu menu, Item clickedItem) { for (VarData data : dataList) { - String varName = Handlers.getPlaceholderHandler().replace(p, data.getName()); + String varName = AbstractMenusApi.get().providers().placeholders().replace(p, data.getName()); if (data.getPlayer() == null) { VariableManagerImpl.instance().deleteGlobal(varName); } else { - String playerName = Handlers.getPlaceholderHandler().replace(p, data.getPlayer()); + String playerName = AbstractMenusApi.get().providers().placeholders().replace(p, data.getPlayer()); VariableManagerImpl.instance().deletePersonal(playerName, varName); } } diff --git a/src/main/java/ru/abstractmenus/data/actions/var/ActionVarSet.java b/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarSet.java similarity index 77% rename from src/main/java/ru/abstractmenus/data/actions/var/ActionVarSet.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarSet.java index 2e3556d..6d61a24 100644 --- a/src/main/java/ru/abstractmenus/data/actions/var/ActionVarSet.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarSet.java @@ -8,7 +8,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.util.TimeUtil; import ru.abstractmenus.variables.VarData; import ru.abstractmenus.variables.VariableManagerImpl; @@ -25,10 +25,10 @@ private ActionVarSet(List dataList) { public void activate(Player p, Menu menu, Item clickedItem) { for (VarData data : dataList) { - String varName = Handlers.getPlaceholderHandler().replace(p, data.getName()); - String varVal = Handlers.getPlaceholderHandler().replace(p, data.getValue()); + String varName = AbstractMenusApi.get().providers().placeholders().replace(p, data.getName()); + String varVal = AbstractMenusApi.get().providers().placeholders().replace(p, data.getValue()); - long time = TimeUtil.parseTime(Handlers.getPlaceholderHandler().replace(p, data.getTime())); + long time = TimeUtil.parseTime(AbstractMenusApi.get().providers().placeholders().replace(p, data.getTime())); boolean replace = data.isReplace().getBool(p, menu); Var var = VariableManagerImpl.instance().createBuilder() @@ -40,7 +40,7 @@ public void activate(Player p, Menu menu, Item clickedItem) { if (data.getPlayer() == null) { VariableManagerImpl.instance().saveGlobal(var, replace); } else { - String playerName = Handlers.getPlaceholderHandler().replace(p, data.getPlayer()); + String playerName = AbstractMenusApi.get().providers().placeholders().replace(p, data.getPlayer()); VariableManagerImpl.instance().savePersonal(playerName, var, replace); } } diff --git a/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpDec.java b/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpDec.java similarity index 89% rename from src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpDec.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpDec.java index 597328f..8e52c7d 100644 --- a/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpDec.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpDec.java @@ -2,7 +2,7 @@ import org.bukkit.entity.Player; import ru.abstractmenus.api.Action; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.hocon.api.ConfigNode; @@ -24,7 +24,7 @@ private ActionVarpDec(List dataList) { public void activate(Player p, Menu menu, Item clickedItem) { for (VarNumData data : dataList) { - String varName = Handlers.getPlaceholderHandler().replace(p, data.getName()); + String varName = AbstractMenusApi.get().providers().placeholders().replace(p, data.getName()); double value = data.getValue().getDouble(p, menu); Function func = num -> num - value; diff --git a/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpDiv.java b/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpDiv.java similarity index 91% rename from src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpDiv.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpDiv.java index 0854ba6..869cf3a 100644 --- a/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpDiv.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpDiv.java @@ -2,7 +2,7 @@ import org.bukkit.entity.Player; import ru.abstractmenus.api.Action; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.Logger; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; @@ -25,7 +25,7 @@ private ActionVarpDiv(List dataList) { public void activate(Player p, Menu menu, Item clickedItem) { for (VarNumData data : dataList) { - String varName = Handlers.getPlaceholderHandler().replace(p, data.getName()); + String varName = AbstractMenusApi.get().providers().placeholders().replace(p, data.getName()); double value = data.getValue().getDouble(p, menu); if (value == 0) { diff --git a/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpInc.java b/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpInc.java similarity index 89% rename from src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpInc.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpInc.java index 6233d34..dbeb048 100644 --- a/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpInc.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpInc.java @@ -2,7 +2,7 @@ import org.bukkit.entity.Player; import ru.abstractmenus.api.Action; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.hocon.api.ConfigNode; @@ -24,7 +24,7 @@ private ActionVarpInc(List dataList) { public void activate(Player p, Menu menu, Item clickedItem) { for (VarNumData data : dataList) { - String varName = Handlers.getPlaceholderHandler().replace(p, data.getName()); + String varName = AbstractMenusApi.get().providers().placeholders().replace(p, data.getName()); double value = data.getValue().getDouble(p, menu); Function func = num -> num + value; diff --git a/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpMul.java b/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpMul.java similarity index 89% rename from src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpMul.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpMul.java index 10f3ebb..2ca0fbf 100644 --- a/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpMul.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpMul.java @@ -2,7 +2,7 @@ import org.bukkit.entity.Player; import ru.abstractmenus.api.Action; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.hocon.api.ConfigNode; @@ -24,7 +24,7 @@ private ActionVarpMul(List dataList) { public void activate(Player p, Menu menu, Item clickedItem) { for (VarNumData data : dataList) { - String varName = Handlers.getPlaceholderHandler().replace(p, data.getName()); + String varName = AbstractMenusApi.get().providers().placeholders().replace(p, data.getName()); double value = data.getValue().getDouble(p, menu); Function func = num -> num * value; diff --git a/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpRem.java b/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpRem.java similarity index 88% rename from src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpRem.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpRem.java index ef92b20..5256634 100644 --- a/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpRem.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpRem.java @@ -2,7 +2,7 @@ import org.bukkit.entity.Player; import ru.abstractmenus.api.Action; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.hocon.api.ConfigNode; @@ -23,7 +23,7 @@ private ActionVarpRem(List dataList) { public void activate(Player p, Menu menu, Item clickedItem) { for (VarData data : dataList) { - String varName = Handlers.getPlaceholderHandler().replace(p, data.getName()); + String varName = AbstractMenusApi.get().providers().placeholders().replace(p, data.getName()); VariableManagerImpl.instance().deletePersonal(p.getName(), varName); } diff --git a/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpSet.java b/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpSet.java similarity index 80% rename from src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpSet.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpSet.java index 2c46a30..1bb5fc2 100644 --- a/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpSet.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpSet.java @@ -2,7 +2,7 @@ import org.bukkit.entity.Player; import ru.abstractmenus.api.Action; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.variables.Var; @@ -25,10 +25,10 @@ private ActionVarpSet(List dataList) { public void activate(Player p, Menu menu, Item clickedItem) { for (VarData data : dataList) { - String varName = Handlers.getPlaceholderHandler().replace(p, data.getName()); - String varVal = Handlers.getPlaceholderHandler().replace(p, data.getValue()); + String varName = AbstractMenusApi.get().providers().placeholders().replace(p, data.getName()); + String varVal = AbstractMenusApi.get().providers().placeholders().replace(p, data.getValue()); - long time = TimeUtil.parseTime(Handlers.getPlaceholderHandler().replace(p, data.getTime())); + long time = TimeUtil.parseTime(AbstractMenusApi.get().providers().placeholders().replace(p, data.getTime())); boolean replace = data.isReplace().getBool(p, menu); Var var = VariableManagerImpl.instance().createBuilder() diff --git a/src/main/java/ru/abstractmenus/data/actions/wrappers/ActionBulk.java b/plugin/src/main/java/ru/abstractmenus/data/actions/wrappers/ActionBulk.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/wrappers/ActionBulk.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/wrappers/ActionBulk.java diff --git a/src/main/java/ru/abstractmenus/data/actions/wrappers/ActionDelay.java b/plugin/src/main/java/ru/abstractmenus/data/actions/wrappers/ActionDelay.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/wrappers/ActionDelay.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/wrappers/ActionDelay.java diff --git a/src/main/java/ru/abstractmenus/data/actions/wrappers/ActionPlayerScope.java b/plugin/src/main/java/ru/abstractmenus/data/actions/wrappers/ActionPlayerScope.java similarity index 90% rename from src/main/java/ru/abstractmenus/data/actions/wrappers/ActionPlayerScope.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/wrappers/ActionPlayerScope.java index 086fda0..d63fd85 100644 --- a/src/main/java/ru/abstractmenus/data/actions/wrappers/ActionPlayerScope.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/wrappers/ActionPlayerScope.java @@ -3,7 +3,7 @@ import org.bukkit.Bukkit; import org.bukkit.entity.Player; import ru.abstractmenus.api.Action; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.data.Actions; @@ -23,7 +23,7 @@ public ActionPlayerScope(String playerName, Actions actions) { @Override public void activate(Player player, Menu menu, Item clickedItem) { - String replacedName = Handlers.getPlaceholderHandler().replace(player, playerName); + String replacedName = AbstractMenusApi.get().providers().placeholders().replace(player, playerName); Player target = Bukkit.getPlayerExact(replacedName); if (target != null && target.isOnline()) { diff --git a/src/main/java/ru/abstractmenus/data/actions/wrappers/ActionRandomActions.java b/plugin/src/main/java/ru/abstractmenus/data/actions/wrappers/ActionRandomActions.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/wrappers/ActionRandomActions.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/wrappers/ActionRandomActions.java diff --git a/src/main/java/ru/abstractmenus/data/activators/ActivatorUtil.java b/plugin/src/main/java/ru/abstractmenus/data/activators/ActivatorUtil.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/activators/ActivatorUtil.java rename to plugin/src/main/java/ru/abstractmenus/data/activators/ActivatorUtil.java diff --git a/src/main/java/ru/abstractmenus/data/activators/OpenButton.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenButton.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/activators/OpenButton.java rename to plugin/src/main/java/ru/abstractmenus/data/activators/OpenButton.java diff --git a/src/main/java/ru/abstractmenus/data/activators/OpenChat.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenChat.java similarity index 88% rename from src/main/java/ru/abstractmenus/data/activators/OpenChat.java rename to plugin/src/main/java/ru/abstractmenus/data/activators/OpenChat.java index baccf22..31f0102 100644 --- a/src/main/java/ru/abstractmenus/data/activators/OpenChat.java +++ b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenChat.java @@ -7,7 +7,7 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.player.AsyncPlayerChatEvent; import ru.abstractmenus.api.Activator; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.util.bukkit.BukkitTasks; import java.util.List; @@ -23,7 +23,7 @@ private OpenChat(List messages) { @EventHandler public void onChat(AsyncChatEvent event) { for (String str : messages) { - String replaced = Handlers.getPlaceholderHandler().replace(event.getPlayer(), str); + String replaced = AbstractMenusApi.get().providers().placeholders().replace(event.getPlayer(), str); if (event.signedMessage().message().equalsIgnoreCase(replaced)) { BukkitTasks.runTask(() -> openMenu(null, event.getPlayer())); diff --git a/src/main/java/ru/abstractmenus/data/activators/OpenChatContains.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenChatContains.java similarity index 89% rename from src/main/java/ru/abstractmenus/data/activators/OpenChatContains.java rename to plugin/src/main/java/ru/abstractmenus/data/activators/OpenChatContains.java index e4f554b..24b5ed2 100644 --- a/src/main/java/ru/abstractmenus/data/activators/OpenChatContains.java +++ b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenChatContains.java @@ -8,7 +8,7 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.player.AsyncPlayerChatEvent; import ru.abstractmenus.api.Activator; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.util.bukkit.BukkitTasks; import java.util.List; @@ -24,7 +24,7 @@ private OpenChatContains(List messages) { @EventHandler public void onChat(AsyncChatEvent event) { for (String str : messages) { - String msg = Handlers.getPlaceholderHandler().replace(event.getPlayer(), str); + String msg = AbstractMenusApi.get().providers().placeholders().replace(event.getPlayer(), str); if (event.signedMessage().message().contains(msg)) { BukkitTasks.runTask(() -> openMenu(null, event.getPlayer())); diff --git a/src/main/java/ru/abstractmenus/data/activators/OpenClickBlock.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenClickBlock.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/activators/OpenClickBlock.java rename to plugin/src/main/java/ru/abstractmenus/data/activators/OpenClickBlock.java diff --git a/src/main/java/ru/abstractmenus/data/activators/OpenClickBlockType.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenClickBlockType.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/activators/OpenClickBlockType.java rename to plugin/src/main/java/ru/abstractmenus/data/activators/OpenClickBlockType.java diff --git a/src/main/java/ru/abstractmenus/data/activators/OpenClickEntity.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenClickEntity.java similarity index 91% rename from src/main/java/ru/abstractmenus/data/activators/OpenClickEntity.java rename to plugin/src/main/java/ru/abstractmenus/data/activators/OpenClickEntity.java index d229fc4..0aad579 100644 --- a/src/main/java/ru/abstractmenus/data/activators/OpenClickEntity.java +++ b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenClickEntity.java @@ -5,7 +5,7 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.player.PlayerInteractEntityEvent; import ru.abstractmenus.api.Activator; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.ValueExtractor; import ru.abstractmenus.data.EntityData; import ru.abstractmenus.extractors.EntityExtractor; @@ -38,7 +38,7 @@ public void onEntityClick(PlayerInteractEntityEvent event) { } if (data.getName() != null) { - String expectedName = Handlers.getPlaceholderHandler().replace(player, data.getName()); + String expectedName = AbstractMenusApi.get().providers().placeholders().replace(player, data.getName()); if (!clickedEntity.getName().equalsIgnoreCase(expectedName)) { continue; } diff --git a/src/main/java/ru/abstractmenus/data/activators/OpenClickItem.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenClickItem.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/activators/OpenClickItem.java rename to plugin/src/main/java/ru/abstractmenus/data/activators/OpenClickItem.java diff --git a/src/main/java/ru/abstractmenus/data/activators/OpenClickNPC.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenClickNPC.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/activators/OpenClickNPC.java rename to plugin/src/main/java/ru/abstractmenus/data/activators/OpenClickNPC.java diff --git a/src/main/java/ru/abstractmenus/data/activators/OpenCommand.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenCommand.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/activators/OpenCommand.java rename to plugin/src/main/java/ru/abstractmenus/data/activators/OpenCommand.java diff --git a/src/main/java/ru/abstractmenus/data/activators/OpenJoin.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenJoin.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/activators/OpenJoin.java rename to plugin/src/main/java/ru/abstractmenus/data/activators/OpenJoin.java diff --git a/src/main/java/ru/abstractmenus/data/activators/OpenLever.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenLever.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/activators/OpenLever.java rename to plugin/src/main/java/ru/abstractmenus/data/activators/OpenLever.java diff --git a/src/main/java/ru/abstractmenus/data/activators/OpenPlate.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenPlate.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/activators/OpenPlate.java rename to plugin/src/main/java/ru/abstractmenus/data/activators/OpenPlate.java diff --git a/src/main/java/ru/abstractmenus/data/activators/OpenRegionEnter.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenRegionEnter.java similarity index 88% rename from src/main/java/ru/abstractmenus/data/activators/OpenRegionEnter.java rename to plugin/src/main/java/ru/abstractmenus/data/activators/OpenRegionEnter.java index 680562b..34c10d2 100644 --- a/src/main/java/ru/abstractmenus/data/activators/OpenRegionEnter.java +++ b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenRegionEnter.java @@ -7,7 +7,7 @@ import ru.abstractmenus.hocon.api.serialize.NodeSerializer; import org.bukkit.event.EventHandler; import ru.abstractmenus.api.Activator; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.extractors.RegionExtractor; import java.util.List; @@ -22,7 +22,7 @@ private OpenRegionEnter(List regions) { @EventHandler public void onRegionJoin(RegionEnterEvent event) { - List regions = Handlers.getPlaceholderHandler().replace(event.getPlayer(), this.regions); + List regions = AbstractMenusApi.get().providers().placeholders().replace(event.getPlayer(), this.regions); if (regions.contains(event.getRegion().getId())) { openMenu(event.getRegion(), event.getPlayer()); } diff --git a/src/main/java/ru/abstractmenus/data/activators/OpenRegionLeave.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenRegionLeave.java similarity index 88% rename from src/main/java/ru/abstractmenus/data/activators/OpenRegionLeave.java rename to plugin/src/main/java/ru/abstractmenus/data/activators/OpenRegionLeave.java index 3c63818..e895719 100644 --- a/src/main/java/ru/abstractmenus/data/activators/OpenRegionLeave.java +++ b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenRegionLeave.java @@ -7,7 +7,7 @@ import ru.abstractmenus.hocon.api.serialize.NodeSerializer; import org.bukkit.event.EventHandler; import ru.abstractmenus.api.Activator; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.extractors.RegionExtractor; import java.util.List; @@ -22,7 +22,7 @@ private OpenRegionLeave(List regions) { @EventHandler public void onRegionEnter(RegionLeaveEvent event) { - List regions = Handlers.getPlaceholderHandler().replace(event.getPlayer(), this.regions); + List regions = AbstractMenusApi.get().providers().placeholders().replace(event.getPlayer(), this.regions); if (regions.contains(event.getRegion().getId())) { openMenu(event.getRegion(), event.getPlayer()); diff --git a/src/main/java/ru/abstractmenus/data/activators/OpenShiftClickEntity.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenShiftClickEntity.java similarity index 92% rename from src/main/java/ru/abstractmenus/data/activators/OpenShiftClickEntity.java rename to plugin/src/main/java/ru/abstractmenus/data/activators/OpenShiftClickEntity.java index c4327b6..092b4e0 100644 --- a/src/main/java/ru/abstractmenus/data/activators/OpenShiftClickEntity.java +++ b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenShiftClickEntity.java @@ -5,7 +5,7 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.player.PlayerInteractEntityEvent; import ru.abstractmenus.api.Activator; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.ValueExtractor; import ru.abstractmenus.data.EntityData; import ru.abstractmenus.extractors.EntityExtractor; @@ -39,7 +39,7 @@ public void onEntityClick(PlayerInteractEntityEvent event) { } if (data.getName() == null) { - String name = Handlers.getPlaceholderHandler().replace(player, data.getName()); + String name = AbstractMenusApi.get().providers().placeholders().replace(player, data.getName()); if (clickedEntity.getName().equalsIgnoreCase(name)) { openMenu(clickedEntity, player); return; diff --git a/src/main/java/ru/abstractmenus/data/activators/OpenSign.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenSign.java similarity index 93% rename from src/main/java/ru/abstractmenus/data/activators/OpenSign.java rename to plugin/src/main/java/ru/abstractmenus/data/activators/OpenSign.java index a47428c..6977a20 100644 --- a/src/main/java/ru/abstractmenus/data/activators/OpenSign.java +++ b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenSign.java @@ -8,7 +8,7 @@ import org.bukkit.event.block.Action; import org.bukkit.event.player.PlayerInteractEvent; import ru.abstractmenus.api.Activator; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.ValueExtractor; import ru.abstractmenus.api.text.Colors; import ru.abstractmenus.extractors.BlockExtractor; @@ -43,7 +43,7 @@ public void onTableClick(PlayerInteractEvent event) { String[] lines = sign.getLines(); int compareLines = Math.min(text.size(), lines.length); for (int i = 0; i < compareLines; i++) { - String line = Handlers.getPlaceholderHandler().replace(player, text.get(i)); + String line = AbstractMenusApi.get().providers().placeholders().replace(player, text.get(i)); if (!line.equalsIgnoreCase(lines[i])) return; } diff --git a/src/main/java/ru/abstractmenus/data/activators/OpenSwapItems.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenSwapItems.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/activators/OpenSwapItems.java rename to plugin/src/main/java/ru/abstractmenus/data/activators/OpenSwapItems.java diff --git a/src/main/java/ru/abstractmenus/data/catalogs/EntityCatalog.java b/plugin/src/main/java/ru/abstractmenus/data/catalogs/EntityCatalog.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/catalogs/EntityCatalog.java rename to plugin/src/main/java/ru/abstractmenus/data/catalogs/EntityCatalog.java diff --git a/src/main/java/ru/abstractmenus/data/catalogs/IteratorCatalog.java b/plugin/src/main/java/ru/abstractmenus/data/catalogs/IteratorCatalog.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/catalogs/IteratorCatalog.java rename to plugin/src/main/java/ru/abstractmenus/data/catalogs/IteratorCatalog.java diff --git a/src/main/java/ru/abstractmenus/data/catalogs/PlayerCatalog.java b/plugin/src/main/java/ru/abstractmenus/data/catalogs/PlayerCatalog.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/catalogs/PlayerCatalog.java rename to plugin/src/main/java/ru/abstractmenus/data/catalogs/PlayerCatalog.java diff --git a/src/main/java/ru/abstractmenus/data/catalogs/ServerCatalog.java b/plugin/src/main/java/ru/abstractmenus/data/catalogs/ServerCatalog.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/catalogs/ServerCatalog.java rename to plugin/src/main/java/ru/abstractmenus/data/catalogs/ServerCatalog.java diff --git a/src/main/java/ru/abstractmenus/data/catalogs/SliceCatalog.java b/plugin/src/main/java/ru/abstractmenus/data/catalogs/SliceCatalog.java similarity index 93% rename from src/main/java/ru/abstractmenus/data/catalogs/SliceCatalog.java rename to plugin/src/main/java/ru/abstractmenus/data/catalogs/SliceCatalog.java index b0ecf53..0720d63 100644 --- a/src/main/java/ru/abstractmenus/data/catalogs/SliceCatalog.java +++ b/plugin/src/main/java/ru/abstractmenus/data/catalogs/SliceCatalog.java @@ -2,7 +2,7 @@ import org.bukkit.entity.Player; import ru.abstractmenus.api.Catalog; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.ValueExtractor; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.hocon.api.ConfigNode; @@ -36,7 +36,7 @@ public SliceCatalog(String value, String separator, boolean trim) { @Override public Collection snapshot(Player player, Menu menu) { - String replaced = Handlers.getPlaceholderHandler().replace(player, value); + String replaced = AbstractMenusApi.get().providers().placeholders().replace(player, value); String[] values = replaced.split(separator); return Arrays.stream(values) .filter(val -> !val.isEmpty()) diff --git a/src/main/java/ru/abstractmenus/data/catalogs/WorldCatalog.java b/plugin/src/main/java/ru/abstractmenus/data/catalogs/WorldCatalog.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/catalogs/WorldCatalog.java rename to plugin/src/main/java/ru/abstractmenus/data/catalogs/WorldCatalog.java diff --git a/src/main/java/ru/abstractmenus/data/comparator/BooleanEvaluator.java b/plugin/src/main/java/ru/abstractmenus/data/comparator/BooleanEvaluator.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/comparator/BooleanEvaluator.java rename to plugin/src/main/java/ru/abstractmenus/data/comparator/BooleanEvaluator.java diff --git a/src/main/java/ru/abstractmenus/data/comparator/LegacyValueComparator.java b/plugin/src/main/java/ru/abstractmenus/data/comparator/LegacyValueComparator.java similarity index 90% rename from src/main/java/ru/abstractmenus/data/comparator/LegacyValueComparator.java rename to plugin/src/main/java/ru/abstractmenus/data/comparator/LegacyValueComparator.java index 8b2874e..74eec4d 100644 --- a/src/main/java/ru/abstractmenus/data/comparator/LegacyValueComparator.java +++ b/plugin/src/main/java/ru/abstractmenus/data/comparator/LegacyValueComparator.java @@ -8,7 +8,7 @@ import ru.abstractmenus.hocon.api.serialize.NodeSerializer; import org.bukkit.entity.Player; import ru.abstractmenus.api.inventory.Menu; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import java.util.List; @@ -49,11 +49,11 @@ public Comparator(String param) { } public boolean compare(Player player, Menu menu){ - String param = Handlers.getPlaceholderHandler().replace(player, getParam()); + String param = AbstractMenusApi.get().providers().placeholders().replace(player, getParam()); if(equals != null){ for(String str : equals){ - String val = Handlers.getPlaceholderHandler().replace(player, str); + String val = AbstractMenusApi.get().providers().placeholders().replace(player, str); try{ if(Double.parseDouble(param) == Double.parseDouble(val)) return true; @@ -65,7 +65,7 @@ public boolean compare(Player player, Menu menu){ if(equalsIgnoreCase != null){ for(String str : equalsIgnoreCase){ - String val = Handlers.getPlaceholderHandler().replace(player, str); + String val = AbstractMenusApi.get().providers().placeholders().replace(player, str); if(param.equalsIgnoreCase(val)){ return true; } @@ -74,7 +74,7 @@ public boolean compare(Player player, Menu menu){ if(contains != null){ for(String str : contains){ - String val = Handlers.getPlaceholderHandler().replace(player, str); + String val = AbstractMenusApi.get().providers().placeholders().replace(player, str); if(param.toLowerCase().contains(val.toLowerCase())){ return true; } diff --git a/src/main/java/ru/abstractmenus/data/comparator/ModernValueComparator.java b/plugin/src/main/java/ru/abstractmenus/data/comparator/ModernValueComparator.java similarity index 87% rename from src/main/java/ru/abstractmenus/data/comparator/ModernValueComparator.java rename to plugin/src/main/java/ru/abstractmenus/data/comparator/ModernValueComparator.java index 8b9a80c..65e472d 100644 --- a/src/main/java/ru/abstractmenus/data/comparator/ModernValueComparator.java +++ b/plugin/src/main/java/ru/abstractmenus/data/comparator/ModernValueComparator.java @@ -5,7 +5,7 @@ import ru.abstractmenus.hocon.api.serialize.NodeSerializer; import org.bukkit.entity.Player; import ru.abstractmenus.api.inventory.Menu; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; public final class ModernValueComparator implements ValueComparator { @@ -18,7 +18,7 @@ private ModernValueComparator(String expression) { @Override public boolean compare(Player player, Menu menu) { - String replaced = Handlers.getPlaceholderHandler().replace(player, expression); + String replaced = AbstractMenusApi.get().providers().placeholders().replace(player, expression); String result = EVALUATOR.evaluate(replaced); return Boolean.parseBoolean(result); } diff --git a/src/main/java/ru/abstractmenus/data/comparator/ValueComparator.java b/plugin/src/main/java/ru/abstractmenus/data/comparator/ValueComparator.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/comparator/ValueComparator.java rename to plugin/src/main/java/ru/abstractmenus/data/comparator/ValueComparator.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropAttributeModifier.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropAttributeModifier.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropAttributeModifier.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropAttributeModifier.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropBannerData.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropBannerData.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropBannerData.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropBannerData.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropBindings.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropBindings.java similarity index 95% rename from src/main/java/ru/abstractmenus/data/properties/PropBindings.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropBindings.java index 6b07b64..a51bfcf 100644 --- a/src/main/java/ru/abstractmenus/data/properties/PropBindings.java +++ b/plugin/src/main/java/ru/abstractmenus/data/properties/PropBindings.java @@ -8,10 +8,10 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.Rule; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.ItemProperty; -import ru.abstractmenus.api.Types; import ru.abstractmenus.menu.item.SimpleItem; import ru.abstractmenus.data.rules.logical.RuleAnd; @@ -86,7 +86,7 @@ public BindGroup deserialize(Class type, ConfigNode node) throws NodeSerializeEx if (Actions.isReserved(key)) continue; - Class token = Types.getItemPropertyType(key); + Class token = AbstractMenusApi.get().itemProperties().get(key); if (token != null) { properties.add(entry.getValue().getValue(token)); diff --git a/src/main/java/ru/abstractmenus/data/properties/PropBookData.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropBookData.java similarity index 78% rename from src/main/java/ru/abstractmenus/data/properties/PropBookData.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropBookData.java index 9bf97bf..f2461d4 100644 --- a/src/main/java/ru/abstractmenus/data/properties/PropBookData.java +++ b/plugin/src/main/java/ru/abstractmenus/data/properties/PropBookData.java @@ -10,7 +10,7 @@ import org.bukkit.inventory.meta.ItemMeta; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.ItemProperty; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import java.util.List; @@ -35,9 +35,9 @@ public boolean isApplyMeta() { @Override public void apply(ItemStack itemStack, ItemMeta meta, Player player, Menu menu) { if (meta instanceof BookMeta) { - String author = Handlers.getPlaceholderHandler().replace(player, data.getAuthor()); - String title = Handlers.getPlaceholderHandler().replace(player, data.getTitle()); - List pages = Handlers.getPlaceholderHandler().replace(player, data.getPages()); + String author = AbstractMenusApi.get().providers().placeholders().replace(player, data.getAuthor()); + String title = AbstractMenusApi.get().providers().placeholders().replace(player, data.getTitle()); + List pages = AbstractMenusApi.get().providers().placeholders().replace(player, data.getPages()); ((BookMeta) meta).setAuthor(author); ((BookMeta) meta).setTitle(title); diff --git a/src/main/java/ru/abstractmenus/data/properties/PropColor.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropColor.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropColor.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropColor.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropCount.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropCount.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropCount.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropCount.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropDamage.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropDamage.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropDamage.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropDamage.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropData.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropData.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropData.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropData.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropEnchantStore.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropEnchantStore.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropEnchantStore.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropEnchantStore.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropEnchantments.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropEnchantments.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropEnchantments.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropEnchantments.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropEquipItem.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropEquipItem.java similarity index 95% rename from src/main/java/ru/abstractmenus/data/properties/PropEquipItem.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropEquipItem.java index 8d23b23..44c4d31 100644 --- a/src/main/java/ru/abstractmenus/data/properties/PropEquipItem.java +++ b/plugin/src/main/java/ru/abstractmenus/data/properties/PropEquipItem.java @@ -5,7 +5,7 @@ import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.Logger; import ru.abstractmenus.api.inventory.ItemProperty; import ru.abstractmenus.api.inventory.Menu; @@ -60,7 +60,7 @@ public void apply(ItemStack item, ItemMeta meta, Player player, Menu menu) { Player target = player; if (playerName != null) { - String replaced = Handlers.getPlaceholderHandler().replace(player, playerName); + String replaced = AbstractMenusApi.get().providers().placeholders().replace(player, playerName); Player found = Bukkit.getPlayerExact(replaced); if (found == null || !found.isOnline()) { diff --git a/src/main/java/ru/abstractmenus/data/properties/PropFireworkData.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropFireworkData.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropFireworkData.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropFireworkData.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropFlags.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropFlags.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropFlags.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropFlags.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropGlow.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropGlow.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropGlow.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropGlow.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropHDB.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropHDB.java similarity index 93% rename from src/main/java/ru/abstractmenus/data/properties/PropHDB.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropHDB.java index 14bcf12..9e7f37c 100644 --- a/src/main/java/ru/abstractmenus/data/properties/PropHDB.java +++ b/plugin/src/main/java/ru/abstractmenus/data/properties/PropHDB.java @@ -10,7 +10,7 @@ import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.ItemProperty; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.util.bukkit.ItemUtil; import ru.abstractmenus.util.bukkit.Skulls; @@ -48,7 +48,7 @@ public boolean isApplyMeta() { @Override public void apply(ItemStack item, ItemMeta meta, Player player, Menu menu) { - String replaced = Handlers.getPlaceholderHandler().replace(player, id); + String replaced = AbstractMenusApi.get().providers().placeholders().replace(player, id); ItemStack headItem = api().getItemHead(replaced); if (headItem == null) diff --git a/src/main/java/ru/abstractmenus/data/properties/PropItemsAdder.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropItemsAdder.java similarity index 91% rename from src/main/java/ru/abstractmenus/data/properties/PropItemsAdder.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropItemsAdder.java index 4949967..7b2ae92 100644 --- a/src/main/java/ru/abstractmenus/data/properties/PropItemsAdder.java +++ b/plugin/src/main/java/ru/abstractmenus/data/properties/PropItemsAdder.java @@ -10,7 +10,7 @@ import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.ItemProperty; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.util.bukkit.ItemUtil; public class PropItemsAdder implements ItemProperty { @@ -33,7 +33,7 @@ public boolean isApplyMeta() { @Override public void apply(ItemStack item, ItemMeta meta, Player player, Menu menu) { - String id = Handlers.getPlaceholderHandler().replace(player, this.id); + String id = AbstractMenusApi.get().providers().placeholders().replace(player, this.id); CustomStack stack = CustomStack.getInstance(id); if (stack == null) diff --git a/src/main/java/ru/abstractmenus/data/properties/PropKnowledgeBook.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropKnowledgeBook.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropKnowledgeBook.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropKnowledgeBook.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropLPMeta.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropLPMeta.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropLPMeta.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropLPMeta.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropLore.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropLore.java similarity index 94% rename from src/main/java/ru/abstractmenus/data/properties/PropLore.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropLore.java index 62a9528..c21760f 100644 --- a/src/main/java/ru/abstractmenus/data/properties/PropLore.java +++ b/plugin/src/main/java/ru/abstractmenus/data/properties/PropLore.java @@ -9,7 +9,7 @@ import org.bukkit.inventory.meta.ItemMeta; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.ItemProperty; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.text.Colors; import ru.abstractmenus.util.MiniMessageUtil; @@ -68,7 +68,7 @@ public void apply(ItemStack itemStack, ItemMeta meta, Player player, Menu menu) meta.setLore(preFormatted); return; } - List replaced = Handlers.getPlaceholderHandler().replace(player, lore); + List replaced = AbstractMenusApi.get().providers().placeholders().replace(player, lore); meta.setLore(MiniMessageUtil.parseToLegacy(replaced)); } diff --git a/src/main/java/ru/abstractmenus/data/properties/PropLoreLight.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropLoreLight.java similarity index 89% rename from src/main/java/ru/abstractmenus/data/properties/PropLoreLight.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropLoreLight.java index 73984b3..663f650 100644 --- a/src/main/java/ru/abstractmenus/data/properties/PropLoreLight.java +++ b/plugin/src/main/java/ru/abstractmenus/data/properties/PropLoreLight.java @@ -3,7 +3,7 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.inventory.ItemProperty; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.text.Colors; @@ -33,7 +33,7 @@ public boolean isApplyMeta() { @Override public void apply(ItemStack itemStack, ItemMeta meta, Player player, Menu menu) { - List replaced = Handlers.getPlaceholderHandler().replace(player, lore); + List replaced = AbstractMenusApi.get().providers().placeholders().replace(player, lore); meta.setLore(replaced); } diff --git a/src/main/java/ru/abstractmenus/data/properties/PropMaterial.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropMaterial.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropMaterial.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropMaterial.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropMmoItem.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropMmoItem.java similarity index 91% rename from src/main/java/ru/abstractmenus/data/properties/PropMmoItem.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropMmoItem.java index 68999c6..cc54614 100644 --- a/src/main/java/ru/abstractmenus/data/properties/PropMmoItem.java +++ b/plugin/src/main/java/ru/abstractmenus/data/properties/PropMmoItem.java @@ -10,7 +10,7 @@ import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.ItemProperty; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.Logger; import ru.abstractmenus.util.bukkit.ItemUtil; @@ -36,7 +36,7 @@ public boolean isApplyMeta() { @Override public void apply(ItemStack item, ItemMeta meta, Player player, Menu menu) { - String[] arr = Handlers.getPlaceholderHandler().replace(player, id).split(":"); + String[] arr = AbstractMenusApi.get().providers().placeholders().replace(player, id).split(":"); if(arr.length >= 2) { ItemStack mmoItem = MMOItems.plugin.getItem(MMOItems.plugin.getTypes().get(arr[0]), arr[1]); diff --git a/src/main/java/ru/abstractmenus/data/properties/PropModel.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropModel.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropModel.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropModel.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropName.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropName.java similarity index 94% rename from src/main/java/ru/abstractmenus/data/properties/PropName.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropName.java index 47c0af7..bda8de0 100644 --- a/src/main/java/ru/abstractmenus/data/properties/PropName.java +++ b/plugin/src/main/java/ru/abstractmenus/data/properties/PropName.java @@ -9,7 +9,7 @@ import org.bukkit.inventory.meta.ItemMeta; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.ItemProperty; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.text.Colors; import ru.abstractmenus.util.MiniMessageUtil; @@ -58,7 +58,7 @@ public void apply(ItemStack itemStack, ItemMeta meta, Player player, Menu menu) meta.setDisplayName(preFormatted); return; } - String replaced = Handlers.getPlaceholderHandler().replace(player, name); + String replaced = AbstractMenusApi.get().providers().placeholders().replace(player, name); meta.setDisplayName(MiniMessageUtil.parseToLegacy(replaced)); } diff --git a/src/main/java/ru/abstractmenus/data/properties/PropNameLight.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropNameLight.java similarity index 89% rename from src/main/java/ru/abstractmenus/data/properties/PropNameLight.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropNameLight.java index af2cecb..85100f6 100644 --- a/src/main/java/ru/abstractmenus/data/properties/PropNameLight.java +++ b/plugin/src/main/java/ru/abstractmenus/data/properties/PropNameLight.java @@ -3,7 +3,7 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.inventory.ItemProperty; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.text.Colors; @@ -31,7 +31,7 @@ public boolean isApplyMeta() { @Override public void apply(ItemStack itemStack, ItemMeta meta, Player player, Menu menu) { - String replaced = Handlers.getPlaceholderHandler().replace(player, name); + String replaced = AbstractMenusApi.get().providers().placeholders().replace(player, name); meta.setDisplayName(replaced); } diff --git a/src/main/java/ru/abstractmenus/data/properties/PropNbt.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropNbt.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropNbt.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropNbt.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropOraxen.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropOraxen.java similarity index 94% rename from src/main/java/ru/abstractmenus/data/properties/PropOraxen.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropOraxen.java index 91823c7..8ef134b 100644 --- a/src/main/java/ru/abstractmenus/data/properties/PropOraxen.java +++ b/plugin/src/main/java/ru/abstractmenus/data/properties/PropOraxen.java @@ -10,7 +10,7 @@ import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.ItemProperty; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.util.bukkit.ItemUtil; import java.lang.invoke.MethodHandle; @@ -62,7 +62,7 @@ public void apply(ItemStack item, ItemMeta meta, Player player, Menu menu) { } private ItemStack getItem(Player player) { - String id = Handlers.getPlaceholderHandler().replace(player, this.id); + String id = AbstractMenusApi.get().providers().placeholders().replace(player, this.id); try { Object builder = getItemByIdMethod.invoke(id); if (builder == null) diff --git a/src/main/java/ru/abstractmenus/data/properties/PropPotionData.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropPotionData.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropPotionData.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropPotionData.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropSerialized.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropSerialized.java similarity index 91% rename from src/main/java/ru/abstractmenus/data/properties/PropSerialized.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropSerialized.java index 50ae0ff..77e4ca3 100644 --- a/src/main/java/ru/abstractmenus/data/properties/PropSerialized.java +++ b/plugin/src/main/java/ru/abstractmenus/data/properties/PropSerialized.java @@ -4,7 +4,7 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.Logger; import ru.abstractmenus.api.inventory.ItemProperty; import ru.abstractmenus.api.inventory.Menu; @@ -33,7 +33,7 @@ public boolean isApplyMeta() { @Override public void apply(ItemStack item, ItemMeta meta, Player player, Menu menu) { - String base64 = Handlers.getPlaceholderHandler().replace(player, value); + String base64 = AbstractMenusApi.get().providers().placeholders().replace(player, value); ItemStack deserialized = ItemUtil.decodeStack(base64); if (deserialized == null) { diff --git a/src/main/java/ru/abstractmenus/data/properties/PropShieldData.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropShieldData.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropShieldData.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropShieldData.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropSkullOwner.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropSkullOwner.java similarity index 91% rename from src/main/java/ru/abstractmenus/data/properties/PropSkullOwner.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropSkullOwner.java index b8e1c81..5ba3521 100644 --- a/src/main/java/ru/abstractmenus/data/properties/PropSkullOwner.java +++ b/plugin/src/main/java/ru/abstractmenus/data/properties/PropSkullOwner.java @@ -11,7 +11,7 @@ import ru.abstractmenus.util.bukkit.ItemUtil; import ru.abstractmenus.api.inventory.Menu; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.util.bukkit.Skulls; public class PropSkullOwner implements ItemProperty { @@ -34,7 +34,7 @@ public boolean isApplyMeta() { @Override public void apply(ItemStack item, ItemMeta meta, Player player, Menu menu) { - String replaced = Handlers.getPlaceholderHandler().replace(player, owner); + String replaced = AbstractMenusApi.get().providers().placeholders().replace(player, owner); ItemStack skullItem = Skulls.getPlayerSkull(replaced); if (skullItem == null) { diff --git a/src/main/java/ru/abstractmenus/data/properties/PropTexture.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropTexture.java similarity index 95% rename from src/main/java/ru/abstractmenus/data/properties/PropTexture.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropTexture.java index 76c141c..7044b8a 100644 --- a/src/main/java/ru/abstractmenus/data/properties/PropTexture.java +++ b/plugin/src/main/java/ru/abstractmenus/data/properties/PropTexture.java @@ -14,7 +14,7 @@ import ru.abstractmenus.api.inventory.ItemProperty; import ru.abstractmenus.util.bukkit.ItemUtil; import ru.abstractmenus.util.bukkit.Skulls; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import java.net.HttpURLConnection; import java.nio.charset.StandardCharsets; @@ -43,7 +43,7 @@ public boolean isApplyMeta() { @Override public void apply(ItemStack item, ItemMeta meta, Player player, Menu menu) { - String replaced = Handlers.getPlaceholderHandler().replace(player, texture); + String replaced = AbstractMenusApi.get().providers().placeholders().replace(player, texture); ItemStack skullItem = Skulls.getCustomSkull(fetchTextureUrl(replaced)); if (skullItem != null) { diff --git a/src/main/java/ru/abstractmenus/data/properties/PropUnbreakable.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropUnbreakable.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropUnbreakable.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropUnbreakable.java diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleBungeeIsOnline.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleBungeeIsOnline.java similarity index 84% rename from src/main/java/ru/abstractmenus/data/rules/RuleBungeeIsOnline.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleBungeeIsOnline.java index 413958f..469fdae 100644 --- a/src/main/java/ru/abstractmenus/data/rules/RuleBungeeIsOnline.java +++ b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleBungeeIsOnline.java @@ -7,7 +7,7 @@ import ru.abstractmenus.api.Rule; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.services.BungeeManager; public class RuleBungeeIsOnline implements Rule { @@ -20,7 +20,7 @@ private RuleBungeeIsOnline(String server){ @Override public boolean check(Player player, Menu menu, Item clickedItem) { - return BungeeManager.instance().isOnline(Handlers.getPlaceholderHandler().replace(player, server)); + return BungeeManager.instance().isOnline(AbstractMenusApi.get().providers().placeholders().replace(player, server)); } public static class Serializer implements NodeSerializer { diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleBungeeOnline.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleBungeeOnline.java similarity index 85% rename from src/main/java/ru/abstractmenus/data/rules/RuleBungeeOnline.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleBungeeOnline.java index 7212fad..60be6fd 100644 --- a/src/main/java/ru/abstractmenus/data/rules/RuleBungeeOnline.java +++ b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleBungeeOnline.java @@ -8,7 +8,7 @@ import ru.abstractmenus.api.Rule; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.services.BungeeManager; import ru.abstractmenus.datatype.TypeInt; @@ -24,7 +24,7 @@ private RuleBungeeOnline(String server, TypeInt online){ @Override public boolean check(Player player, Menu menu, Item clickedItem) { - return BungeeManager.instance().getOnline(Handlers.getPlaceholderHandler().replace(player, server)) >= online.getInt(player, menu); + return BungeeManager.instance().getOnline(AbstractMenusApi.get().providers().placeholders().replace(player, server)) >= online.getInt(player, menu); } public static class Serializer implements NodeSerializer { diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleChance.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleChance.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/RuleChance.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleChance.java diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleExistVar.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleExistVar.java similarity index 85% rename from src/main/java/ru/abstractmenus/data/rules/RuleExistVar.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleExistVar.java index 01056d8..d368571 100644 --- a/src/main/java/ru/abstractmenus/data/rules/RuleExistVar.java +++ b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleExistVar.java @@ -7,7 +7,7 @@ import ru.abstractmenus.api.Rule; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.variables.VariableManagerImpl; public class RuleExistVar implements Rule { @@ -22,12 +22,12 @@ private RuleExistVar(String player, String name){ @Override public boolean check(Player p, Menu menu, Item clickedItem) { - String varName = Handlers.getPlaceholderHandler().replace(p, this.name); + String varName = AbstractMenusApi.get().providers().placeholders().replace(p, this.name); if(this.player == null) { return VariableManagerImpl.instance().getGlobal(varName) != null; } else { - String varPlayer = Handlers.getPlaceholderHandler().replace(p, this.player); + String varPlayer = AbstractMenusApi.get().providers().placeholders().replace(p, this.player); return VariableManagerImpl.instance().getPersonal(varPlayer, varName) != null; } } diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleExistVarp.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleExistVarp.java similarity index 87% rename from src/main/java/ru/abstractmenus/data/rules/RuleExistVarp.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleExistVarp.java index 169d0ad..dc44f77 100644 --- a/src/main/java/ru/abstractmenus/data/rules/RuleExistVarp.java +++ b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleExistVarp.java @@ -1,7 +1,7 @@ package ru.abstractmenus.data.rules; import org.bukkit.entity.Player; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.Rule; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; @@ -20,7 +20,7 @@ private RuleExistVarp(String name){ @Override public boolean check(Player p, Menu menu, Item clickedItem) { - String varName = Handlers.getPlaceholderHandler().replace(p, this.name); + String varName = AbstractMenusApi.get().providers().placeholders().replace(p, this.name); return VariableManagerImpl.instance().getPersonal(p.getName(), varName) != null; } diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleFoodLevel.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleFoodLevel.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/RuleFoodLevel.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleFoodLevel.java diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleFreeSlot.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleFreeSlot.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/RuleFreeSlot.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleFreeSlot.java diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleFreeSlotCount.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleFreeSlotCount.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/RuleFreeSlotCount.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleFreeSlotCount.java diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleGameMode.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleGameMode.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/RuleGameMode.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleGameMode.java diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleGroup.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleGroup.java similarity index 79% rename from src/main/java/ru/abstractmenus/data/rules/RuleGroup.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleGroup.java index ce13402..95de37f 100644 --- a/src/main/java/ru/abstractmenus/data/rules/RuleGroup.java +++ b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleGroup.java @@ -7,7 +7,7 @@ import ru.abstractmenus.api.Rule; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import java.util.List; @@ -22,8 +22,8 @@ private RuleGroup(List groups) { @Override public boolean check(Player player, Menu menu, Item clickedItem) { for (String group : groups) { - String replaced = Handlers.getPlaceholderHandler().replace(player, group); - if (!Handlers.getPermissionsHandler().hasGroup(player, replaced)) return false; + String replaced = AbstractMenusApi.get().providers().placeholders().replace(player, group); + if (!AbstractMenusApi.get().providers().permissions().hasGroup(player, replaced)) return false; } return true; } diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleHealth.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleHealth.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/RuleHealth.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleHealth.java diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleHeldItem.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleHeldItem.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/RuleHeldItem.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleHeldItem.java diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleIf.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleIf.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/RuleIf.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleIf.java diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleInventoryItem.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleInventoryItem.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/RuleInventoryItem.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleInventoryItem.java diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleJS.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleJS.java similarity index 90% rename from src/main/java/ru/abstractmenus/data/rules/RuleJS.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleJS.java index 4086255..f9d41de 100644 --- a/src/main/java/ru/abstractmenus/data/rules/RuleJS.java +++ b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleJS.java @@ -6,7 +6,7 @@ import ru.abstractmenus.api.Rule; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.Logger; import javax.script.*; @@ -41,7 +41,7 @@ private RuleJS(String js){ @Override public boolean check(Player player, Menu menu, Item clickedItem) { try{ - Object result = ENGINE.eval(Handlers.getPlaceholderHandler().replace(player, js), bindings); + Object result = ENGINE.eval(AbstractMenusApi.get().providers().placeholders().replace(player, js), bindings); return result.toString().equals("true"); } catch (ScriptException e){ Logger.severe("Cannot execute JavaScript code: " + e.getMessage()); diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleLevel.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleLevel.java similarity index 85% rename from src/main/java/ru/abstractmenus/data/rules/RuleLevel.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleLevel.java index dba7b2b..86dcd06 100644 --- a/src/main/java/ru/abstractmenus/data/rules/RuleLevel.java +++ b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleLevel.java @@ -7,7 +7,7 @@ import ru.abstractmenus.api.Rule; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.datatype.TypeInt; public class RuleLevel implements Rule { @@ -20,7 +20,7 @@ private RuleLevel(TypeInt level){ @Override public boolean check(Player player, Menu menu, Item clickedItem) { - return Handlers.getLevelHandler().getLevel(player) >= level.getInt(player, menu); + return AbstractMenusApi.get().providers().levels().getLevel(player) >= level.getInt(player, menu); } public static class Serializer implements NodeSerializer { diff --git a/plugin/src/main/java/ru/abstractmenus/data/rules/RuleMoney.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleMoney.java new file mode 100644 index 0000000..01ec2b5 --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleMoney.java @@ -0,0 +1,66 @@ +package ru.abstractmenus.data.rules; + +import ru.abstractmenus.hocon.api.ConfigNode; +import ru.abstractmenus.hocon.api.serialize.NodeSerializeException; +import ru.abstractmenus.hocon.api.serialize.NodeSerializer; +import org.bukkit.entity.Player; +import ru.abstractmenus.api.Rule; +import ru.abstractmenus.api.handler.EconomyHandler; +import ru.abstractmenus.api.inventory.Menu; +import ru.abstractmenus.api.inventory.Item; +import ru.abstractmenus.api.AbstractMenusApi; +import ru.abstractmenus.datatype.TypeDouble; + +public class RuleMoney implements Rule { + + private final TypeDouble money; + private final String provider; + + private RuleMoney(TypeDouble money, String provider){ + this.money = money; + this.provider = provider; + } + + @Override + public boolean check(Player player, Menu menu, Item clickedItem) { + EconomyHandler eco = provider != null + ? AbstractMenusApi.get().providers().economy(provider) + : AbstractMenusApi.get().providers().economy(); + if (eco == null) { + return false; + } + return eco.hasBalance(player, money.getDouble(player, menu)); + } + + public static class Serializer implements NodeSerializer { + + @Override + public RuleMoney deserialize(Class type, ConfigNode node) throws NodeSerializeException { + TypeDouble money; + String provider = null; + + if (node.isMap()) { + money = node.node("amount").getValue(TypeDouble.class); + provider = node.node("provider").getString(null); + } else { + money = node.getValue(TypeDouble.class); + } + + if (provider != null) { + if (!AbstractMenusApi.get().providers().hasEconomy(provider)) { + StringBuilder known = new StringBuilder(); + for (EconomyHandler h : AbstractMenusApi.get().providers().allEconomy()) { + if (known.length() > 0) known.append(", "); + known.append(h.getClass().getSimpleName()); + } + throw new NodeSerializeException(node, + "Unknown economy provider '" + provider + "'. Registered: [" + + known + "]. Omit the 'provider' field for default selection."); + } + } + + return new RuleMoney(money, provider); + } + + } +} diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleOnline.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleOnline.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/RuleOnline.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleOnline.java diff --git a/src/main/java/ru/abstractmenus/data/rules/RulePermission.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RulePermission.java similarity index 79% rename from src/main/java/ru/abstractmenus/data/rules/RulePermission.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RulePermission.java index ed619d8..b843268 100644 --- a/src/main/java/ru/abstractmenus/data/rules/RulePermission.java +++ b/plugin/src/main/java/ru/abstractmenus/data/rules/RulePermission.java @@ -7,7 +7,7 @@ import ru.abstractmenus.api.Rule; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import java.util.List; @@ -22,8 +22,8 @@ private RulePermission(List permission){ @Override public boolean check(Player player, Menu menu, Item clickedItem) { for (String perm : permission){ - String replaced = Handlers.getPlaceholderHandler().replace(player, perm); - if(!Handlers.getPermissionsHandler().hasPermission(player, replaced)) return false; + String replaced = AbstractMenusApi.get().providers().placeholders().replace(player, perm); + if(!AbstractMenusApi.get().providers().permissions().hasPermission(player, replaced)) return false; } return true; } diff --git a/src/main/java/ru/abstractmenus/data/rules/RulePlacedItem.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RulePlacedItem.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/RulePlacedItem.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RulePlacedItem.java diff --git a/src/main/java/ru/abstractmenus/data/rules/RulePlayerIsOnline.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RulePlayerIsOnline.java similarity index 87% rename from src/main/java/ru/abstractmenus/data/rules/RulePlayerIsOnline.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RulePlayerIsOnline.java index 8d513e5..939ce1c 100644 --- a/src/main/java/ru/abstractmenus/data/rules/RulePlayerIsOnline.java +++ b/plugin/src/main/java/ru/abstractmenus/data/rules/RulePlayerIsOnline.java @@ -2,7 +2,7 @@ import org.bukkit.Bukkit; import org.bukkit.entity.Player; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.Rule; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; @@ -20,7 +20,7 @@ private RulePlayerIsOnline(String playerName){ @Override public boolean check(Player player, Menu menu, Item clickedItem) { - String replaced = Handlers.getPlaceholderHandler().replace(player, playerName); + String replaced = AbstractMenusApi.get().providers().placeholders().replace(player, playerName); Player foundPlayer = Bukkit.getPlayerExact(replaced); return foundPlayer != null && foundPlayer.isOnline(); diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleRegion.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleRegion.java similarity index 90% rename from src/main/java/ru/abstractmenus/data/rules/RuleRegion.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleRegion.java index d94f5ab..5c47cd7 100644 --- a/src/main/java/ru/abstractmenus/data/rules/RuleRegion.java +++ b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleRegion.java @@ -8,7 +8,7 @@ import ru.abstractmenus.api.Rule; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.util.RegionUtils; import java.util.List; @@ -24,7 +24,7 @@ private RuleRegion(List regions){ @Override public boolean check(Player player, Menu menu, Item clickedItem) { for (String reg : regions){ - String replaced = Handlers.getPlaceholderHandler().replace(player, reg); + String replaced = AbstractMenusApi.get().providers().placeholders().replace(player, reg); ProtectedRegion region = RegionUtils.getRegion(player.getWorld(), replaced); if(region != null && region.contains( diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleWorld.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleWorld.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/RuleWorld.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleWorld.java diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleXp.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleXp.java similarity index 85% rename from src/main/java/ru/abstractmenus/data/rules/RuleXp.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleXp.java index c070494..f9784d0 100644 --- a/src/main/java/ru/abstractmenus/data/rules/RuleXp.java +++ b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleXp.java @@ -7,7 +7,7 @@ import ru.abstractmenus.api.Rule; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.datatype.TypeFloat; public class RuleXp implements Rule { @@ -20,7 +20,7 @@ private RuleXp(TypeFloat xp) { @Override public boolean check(Player player, Menu menu, Item clickedItem) { - return Handlers.getLevelHandler().getXp(player) >= xp.getFloat(player, menu); + return AbstractMenusApi.get().providers().levels().getXp(player) >= xp.getFloat(player, menu); } public static class Serializer implements NodeSerializer { diff --git a/src/main/java/ru/abstractmenus/data/rules/logical/RuleAnd.java b/plugin/src/main/java/ru/abstractmenus/data/rules/logical/RuleAnd.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/logical/RuleAnd.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/logical/RuleAnd.java diff --git a/src/main/java/ru/abstractmenus/data/rules/logical/RuleNot.java b/plugin/src/main/java/ru/abstractmenus/data/rules/logical/RuleNot.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/logical/RuleNot.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/logical/RuleNot.java diff --git a/src/main/java/ru/abstractmenus/data/rules/logical/RuleOneOf.java b/plugin/src/main/java/ru/abstractmenus/data/rules/logical/RuleOneOf.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/logical/RuleOneOf.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/logical/RuleOneOf.java diff --git a/src/main/java/ru/abstractmenus/data/rules/logical/RuleOr.java b/plugin/src/main/java/ru/abstractmenus/data/rules/logical/RuleOr.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/logical/RuleOr.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/logical/RuleOr.java diff --git a/src/main/java/ru/abstractmenus/data/rules/logical/RulePlayerScope.java b/plugin/src/main/java/ru/abstractmenus/data/rules/logical/RulePlayerScope.java similarity index 89% rename from src/main/java/ru/abstractmenus/data/rules/logical/RulePlayerScope.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/logical/RulePlayerScope.java index daf3067..7578af7 100644 --- a/src/main/java/ru/abstractmenus/data/rules/logical/RulePlayerScope.java +++ b/plugin/src/main/java/ru/abstractmenus/data/rules/logical/RulePlayerScope.java @@ -2,7 +2,7 @@ import org.bukkit.Bukkit; import org.bukkit.entity.Player; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.Rule; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; @@ -22,7 +22,7 @@ public RulePlayerScope(String playerName, Rule rules) { @Override public boolean check(Player player, Menu menu, Item clickedItem) { - String replacedName = Handlers.getPlaceholderHandler().replace(player, playerName); + String replacedName = AbstractMenusApi.get().providers().placeholders().replace(player, playerName); Player target = Bukkit.getPlayerExact(replacedName); if (target != null && target.isOnline()) { diff --git a/src/main/java/ru/abstractmenus/data/rules/logical/RulesGroup.java b/plugin/src/main/java/ru/abstractmenus/data/rules/logical/RulesGroup.java similarity index 95% rename from src/main/java/ru/abstractmenus/data/rules/logical/RulesGroup.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/logical/RulesGroup.java index f4a59e2..b7a8194 100644 --- a/src/main/java/ru/abstractmenus/data/rules/logical/RulesGroup.java +++ b/plugin/src/main/java/ru/abstractmenus/data/rules/logical/RulesGroup.java @@ -6,10 +6,10 @@ import ru.abstractmenus.hocon.api.serialize.NodeSerializeException; import ru.abstractmenus.hocon.api.serialize.NodeSerializer; import org.bukkit.entity.Player; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.Rule; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; -import ru.abstractmenus.api.Types; import ru.abstractmenus.util.StringUtil; import java.util.LinkedList; @@ -81,7 +81,7 @@ private Rule getSingleRule(String key, ConfigNode node) throws NodeSerializeExce isNot = true; } - Class type = Types.getRuleType(key); + Class type = AbstractMenusApi.get().rules().get(key); if (type != null) { Rule rule = node.getValue(type); diff --git a/src/main/java/ru/abstractmenus/datatype/DataType.java b/plugin/src/main/java/ru/abstractmenus/datatype/DataType.java similarity index 79% rename from src/main/java/ru/abstractmenus/datatype/DataType.java rename to plugin/src/main/java/ru/abstractmenus/datatype/DataType.java index e304cf4..3fd668d 100644 --- a/src/main/java/ru/abstractmenus/datatype/DataType.java +++ b/plugin/src/main/java/ru/abstractmenus/datatype/DataType.java @@ -3,7 +3,7 @@ import lombok.Getter; import org.bukkit.entity.Player; import ru.abstractmenus.api.inventory.Menu; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; public abstract class DataType implements Cloneable { @@ -15,7 +15,7 @@ public abstract class DataType implements Cloneable { } public String replaceFor(Player player, Menu menu) { - return Handlers.getPlaceholderHandler().replace(player, value); + return AbstractMenusApi.get().providers().placeholders().replace(player, value); } public static boolean hasPlaceholder(String string) { diff --git a/src/main/java/ru/abstractmenus/datatype/TypeBool.java b/plugin/src/main/java/ru/abstractmenus/datatype/TypeBool.java similarity index 100% rename from src/main/java/ru/abstractmenus/datatype/TypeBool.java rename to plugin/src/main/java/ru/abstractmenus/datatype/TypeBool.java diff --git a/src/main/java/ru/abstractmenus/datatype/TypeByte.java b/plugin/src/main/java/ru/abstractmenus/datatype/TypeByte.java similarity index 100% rename from src/main/java/ru/abstractmenus/datatype/TypeByte.java rename to plugin/src/main/java/ru/abstractmenus/datatype/TypeByte.java diff --git a/src/main/java/ru/abstractmenus/datatype/TypeColor.java b/plugin/src/main/java/ru/abstractmenus/datatype/TypeColor.java similarity index 100% rename from src/main/java/ru/abstractmenus/datatype/TypeColor.java rename to plugin/src/main/java/ru/abstractmenus/datatype/TypeColor.java diff --git a/src/main/java/ru/abstractmenus/datatype/TypeDouble.java b/plugin/src/main/java/ru/abstractmenus/datatype/TypeDouble.java similarity index 100% rename from src/main/java/ru/abstractmenus/datatype/TypeDouble.java rename to plugin/src/main/java/ru/abstractmenus/datatype/TypeDouble.java diff --git a/src/main/java/ru/abstractmenus/datatype/TypeEnum.java b/plugin/src/main/java/ru/abstractmenus/datatype/TypeEnum.java similarity index 100% rename from src/main/java/ru/abstractmenus/datatype/TypeEnum.java rename to plugin/src/main/java/ru/abstractmenus/datatype/TypeEnum.java diff --git a/src/main/java/ru/abstractmenus/datatype/TypeFloat.java b/plugin/src/main/java/ru/abstractmenus/datatype/TypeFloat.java similarity index 100% rename from src/main/java/ru/abstractmenus/datatype/TypeFloat.java rename to plugin/src/main/java/ru/abstractmenus/datatype/TypeFloat.java diff --git a/src/main/java/ru/abstractmenus/datatype/TypeInt.java b/plugin/src/main/java/ru/abstractmenus/datatype/TypeInt.java similarity index 100% rename from src/main/java/ru/abstractmenus/datatype/TypeInt.java rename to plugin/src/main/java/ru/abstractmenus/datatype/TypeInt.java diff --git a/src/main/java/ru/abstractmenus/datatype/TypeLocation.java b/plugin/src/main/java/ru/abstractmenus/datatype/TypeLocation.java similarity index 100% rename from src/main/java/ru/abstractmenus/datatype/TypeLocation.java rename to plugin/src/main/java/ru/abstractmenus/datatype/TypeLocation.java diff --git a/src/main/java/ru/abstractmenus/datatype/TypeLong.java b/plugin/src/main/java/ru/abstractmenus/datatype/TypeLong.java similarity index 100% rename from src/main/java/ru/abstractmenus/datatype/TypeLong.java rename to plugin/src/main/java/ru/abstractmenus/datatype/TypeLong.java diff --git a/src/main/java/ru/abstractmenus/datatype/TypeMaterial.java b/plugin/src/main/java/ru/abstractmenus/datatype/TypeMaterial.java similarity index 100% rename from src/main/java/ru/abstractmenus/datatype/TypeMaterial.java rename to plugin/src/main/java/ru/abstractmenus/datatype/TypeMaterial.java diff --git a/src/main/java/ru/abstractmenus/datatype/TypeShort.java b/plugin/src/main/java/ru/abstractmenus/datatype/TypeShort.java similarity index 100% rename from src/main/java/ru/abstractmenus/datatype/TypeShort.java rename to plugin/src/main/java/ru/abstractmenus/datatype/TypeShort.java diff --git a/src/main/java/ru/abstractmenus/datatype/TypeSlot.java b/plugin/src/main/java/ru/abstractmenus/datatype/TypeSlot.java similarity index 100% rename from src/main/java/ru/abstractmenus/datatype/TypeSlot.java rename to plugin/src/main/java/ru/abstractmenus/datatype/TypeSlot.java diff --git a/src/main/java/ru/abstractmenus/datatype/TypeSound.java b/plugin/src/main/java/ru/abstractmenus/datatype/TypeSound.java similarity index 100% rename from src/main/java/ru/abstractmenus/datatype/TypeSound.java rename to plugin/src/main/java/ru/abstractmenus/datatype/TypeSound.java diff --git a/src/main/java/ru/abstractmenus/datatype/TypeWorld.java b/plugin/src/main/java/ru/abstractmenus/datatype/TypeWorld.java similarity index 100% rename from src/main/java/ru/abstractmenus/datatype/TypeWorld.java rename to plugin/src/main/java/ru/abstractmenus/datatype/TypeWorld.java diff --git a/src/main/java/ru/abstractmenus/events/RegionEnterEvent.java b/plugin/src/main/java/ru/abstractmenus/events/RegionEnterEvent.java similarity index 100% rename from src/main/java/ru/abstractmenus/events/RegionEnterEvent.java rename to plugin/src/main/java/ru/abstractmenus/events/RegionEnterEvent.java diff --git a/src/main/java/ru/abstractmenus/events/RegionEvent.java b/plugin/src/main/java/ru/abstractmenus/events/RegionEvent.java similarity index 100% rename from src/main/java/ru/abstractmenus/events/RegionEvent.java rename to plugin/src/main/java/ru/abstractmenus/events/RegionEvent.java diff --git a/src/main/java/ru/abstractmenus/events/RegionLeaveEvent.java b/plugin/src/main/java/ru/abstractmenus/events/RegionLeaveEvent.java similarity index 100% rename from src/main/java/ru/abstractmenus/events/RegionLeaveEvent.java rename to plugin/src/main/java/ru/abstractmenus/events/RegionLeaveEvent.java diff --git a/src/main/java/ru/abstractmenus/extractors/BlockExtractor.java b/plugin/src/main/java/ru/abstractmenus/extractors/BlockExtractor.java similarity index 100% rename from src/main/java/ru/abstractmenus/extractors/BlockExtractor.java rename to plugin/src/main/java/ru/abstractmenus/extractors/BlockExtractor.java diff --git a/src/main/java/ru/abstractmenus/extractors/CommandExtractor.java b/plugin/src/main/java/ru/abstractmenus/extractors/CommandExtractor.java similarity index 100% rename from src/main/java/ru/abstractmenus/extractors/CommandExtractor.java rename to plugin/src/main/java/ru/abstractmenus/extractors/CommandExtractor.java diff --git a/src/main/java/ru/abstractmenus/extractors/EntityExtractor.java b/plugin/src/main/java/ru/abstractmenus/extractors/EntityExtractor.java similarity index 100% rename from src/main/java/ru/abstractmenus/extractors/EntityExtractor.java rename to plugin/src/main/java/ru/abstractmenus/extractors/EntityExtractor.java diff --git a/src/main/java/ru/abstractmenus/extractors/ItemStackExtractor.java b/plugin/src/main/java/ru/abstractmenus/extractors/ItemStackExtractor.java similarity index 100% rename from src/main/java/ru/abstractmenus/extractors/ItemStackExtractor.java rename to plugin/src/main/java/ru/abstractmenus/extractors/ItemStackExtractor.java diff --git a/src/main/java/ru/abstractmenus/extractors/NPCExtractor.java b/plugin/src/main/java/ru/abstractmenus/extractors/NPCExtractor.java similarity index 100% rename from src/main/java/ru/abstractmenus/extractors/NPCExtractor.java rename to plugin/src/main/java/ru/abstractmenus/extractors/NPCExtractor.java diff --git a/src/main/java/ru/abstractmenus/extractors/PlayerExtractor.java b/plugin/src/main/java/ru/abstractmenus/extractors/PlayerExtractor.java similarity index 75% rename from src/main/java/ru/abstractmenus/extractors/PlayerExtractor.java rename to plugin/src/main/java/ru/abstractmenus/extractors/PlayerExtractor.java index d7829ca..4f21728 100644 --- a/src/main/java/ru/abstractmenus/extractors/PlayerExtractor.java +++ b/plugin/src/main/java/ru/abstractmenus/extractors/PlayerExtractor.java @@ -2,7 +2,7 @@ import org.apache.commons.lang3.StringUtils; import org.bukkit.entity.Player; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.ValueExtractor; public class PlayerExtractor implements ValueExtractor { @@ -12,7 +12,7 @@ public class PlayerExtractor implements ValueExtractor { @Override public String extract(Object obj, String placeholder) { return (obj instanceof Player player && player.isOnline()) - ? Handlers.getPlaceholderHandler().replacePlaceholder(player, placeholder) + ? AbstractMenusApi.get().providers().placeholders().replacePlaceholder(player, placeholder) : StringUtils.EMPTY; } } diff --git a/src/main/java/ru/abstractmenus/extractors/RegionExtractor.java b/plugin/src/main/java/ru/abstractmenus/extractors/RegionExtractor.java similarity index 100% rename from src/main/java/ru/abstractmenus/extractors/RegionExtractor.java rename to plugin/src/main/java/ru/abstractmenus/extractors/RegionExtractor.java diff --git a/src/main/java/ru/abstractmenus/extractors/WorldExtractor.java b/plugin/src/main/java/ru/abstractmenus/extractors/WorldExtractor.java similarity index 100% rename from src/main/java/ru/abstractmenus/extractors/WorldExtractor.java rename to plugin/src/main/java/ru/abstractmenus/extractors/WorldExtractor.java diff --git a/src/main/java/ru/abstractmenus/handlers/EconomyVaultHandler.java b/plugin/src/main/java/ru/abstractmenus/handlers/EconomyVaultHandler.java similarity index 100% rename from src/main/java/ru/abstractmenus/handlers/EconomyVaultHandler.java rename to plugin/src/main/java/ru/abstractmenus/handlers/EconomyVaultHandler.java diff --git a/src/main/java/ru/abstractmenus/handlers/LevelDefaultHandler.java b/plugin/src/main/java/ru/abstractmenus/handlers/LevelDefaultHandler.java similarity index 100% rename from src/main/java/ru/abstractmenus/handlers/LevelDefaultHandler.java rename to plugin/src/main/java/ru/abstractmenus/handlers/LevelDefaultHandler.java diff --git a/src/main/java/ru/abstractmenus/handlers/LuckPermsHandler.java b/plugin/src/main/java/ru/abstractmenus/handlers/LuckPermsHandler.java similarity index 100% rename from src/main/java/ru/abstractmenus/handlers/LuckPermsHandler.java rename to plugin/src/main/java/ru/abstractmenus/handlers/LuckPermsHandler.java diff --git a/src/main/java/ru/abstractmenus/handlers/PermissionDefaultHandler.java b/plugin/src/main/java/ru/abstractmenus/handlers/PermissionDefaultHandler.java similarity index 100% rename from src/main/java/ru/abstractmenus/handlers/PermissionDefaultHandler.java rename to plugin/src/main/java/ru/abstractmenus/handlers/PermissionDefaultHandler.java diff --git a/src/main/java/ru/abstractmenus/handlers/SkinsRestorerHandler.java b/plugin/src/main/java/ru/abstractmenus/handlers/SkinsRestorerHandler.java similarity index 100% rename from src/main/java/ru/abstractmenus/handlers/SkinsRestorerHandler.java rename to plugin/src/main/java/ru/abstractmenus/handlers/SkinsRestorerHandler.java diff --git a/src/main/java/ru/abstractmenus/handlers/placeholder/PlaceholderCustomHandler.java b/plugin/src/main/java/ru/abstractmenus/handlers/placeholder/PlaceholderCustomHandler.java similarity index 100% rename from src/main/java/ru/abstractmenus/handlers/placeholder/PlaceholderCustomHandler.java rename to plugin/src/main/java/ru/abstractmenus/handlers/placeholder/PlaceholderCustomHandler.java diff --git a/src/main/java/ru/abstractmenus/handlers/placeholder/PlaceholderDefaultHandler.java b/plugin/src/main/java/ru/abstractmenus/handlers/placeholder/PlaceholderDefaultHandler.java similarity index 100% rename from src/main/java/ru/abstractmenus/handlers/placeholder/PlaceholderDefaultHandler.java rename to plugin/src/main/java/ru/abstractmenus/handlers/placeholder/PlaceholderDefaultHandler.java diff --git a/src/main/java/ru/abstractmenus/listeners/ChatListener.java b/plugin/src/main/java/ru/abstractmenus/listeners/ChatListener.java similarity index 100% rename from src/main/java/ru/abstractmenus/listeners/ChatListener.java rename to plugin/src/main/java/ru/abstractmenus/listeners/ChatListener.java diff --git a/src/main/java/ru/abstractmenus/listeners/InventoryListener.java b/plugin/src/main/java/ru/abstractmenus/listeners/InventoryListener.java similarity index 100% rename from src/main/java/ru/abstractmenus/listeners/InventoryListener.java rename to plugin/src/main/java/ru/abstractmenus/listeners/InventoryListener.java diff --git a/src/main/java/ru/abstractmenus/listeners/PlayerListener.java b/plugin/src/main/java/ru/abstractmenus/listeners/PlayerListener.java similarity index 100% rename from src/main/java/ru/abstractmenus/listeners/PlayerListener.java rename to plugin/src/main/java/ru/abstractmenus/listeners/PlayerListener.java diff --git a/plugin/src/main/java/ru/abstractmenus/listeners/ServerLoadListener.java b/plugin/src/main/java/ru/abstractmenus/listeners/ServerLoadListener.java new file mode 100644 index 0000000..48892b8 --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/listeners/ServerLoadListener.java @@ -0,0 +1,36 @@ +package ru.abstractmenus.listeners; + +import org.bukkit.event.EventHandler; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.server.ServerLoadEvent; +import ru.abstractmenus.AbstractMenus; + +/** + * Defers menu loading until every plugin has finished {@code onEnable}. + * + *

Path 1 addons (plugin-as-addons that {@code depend: AbstractMenus} in + * their {@code plugin.yml}) only run their own {@code onEnable} AFTER AM + * has finished its own. If AM loaded menus inside its {@code onEnable}, + * any HOCON parse-time validation that depends on a Path 1 contribution + * (e.g. {@code provider: "playerpoints"} when PlayerPointsAddon is a + * standalone plugin) would fail because the contribution has not been + * registered yet. + * + *

{@link ServerLoadEvent} fires after every plugin's {@code onEnable} + * has completed - on both server startup ({@code STARTUP} type) and + * {@code /reload} ({@code RELOAD} type) - so by the time we get here, + * Path 1 addons have published whatever they were going to publish. + * + *

The listener unregisters itself after firing once so a hypothetical + * second event (Bukkit reserves the right) does not double-load menus + * mid-session. + */ +public final class ServerLoadListener implements Listener { + + @EventHandler + public void onServerLoad(ServerLoadEvent event) { + AbstractMenus.instance().loadMenus(); + HandlerList.unregisterAll(this); + } +} diff --git a/src/main/java/ru/abstractmenus/listeners/wg/PlayerMoveListener.java b/plugin/src/main/java/ru/abstractmenus/listeners/wg/PlayerMoveListener.java similarity index 100% rename from src/main/java/ru/abstractmenus/listeners/wg/PlayerMoveListener.java rename to plugin/src/main/java/ru/abstractmenus/listeners/wg/PlayerMoveListener.java diff --git a/src/main/java/ru/abstractmenus/listeners/wg/WGHandlers.java b/plugin/src/main/java/ru/abstractmenus/listeners/wg/WGHandlers.java similarity index 100% rename from src/main/java/ru/abstractmenus/listeners/wg/WGHandlers.java rename to plugin/src/main/java/ru/abstractmenus/listeners/wg/WGHandlers.java diff --git a/src/main/java/ru/abstractmenus/listeners/wg/WgRegionHandler.java b/plugin/src/main/java/ru/abstractmenus/listeners/wg/WgRegionHandler.java similarity index 100% rename from src/main/java/ru/abstractmenus/listeners/wg/WgRegionHandler.java rename to plugin/src/main/java/ru/abstractmenus/listeners/wg/WgRegionHandler.java diff --git a/src/main/java/ru/abstractmenus/menu/AbstractMenu.java b/plugin/src/main/java/ru/abstractmenus/menu/AbstractMenu.java similarity index 98% rename from src/main/java/ru/abstractmenus/menu/AbstractMenu.java rename to plugin/src/main/java/ru/abstractmenus/menu/AbstractMenu.java index 829101c..6bddde4 100644 --- a/src/main/java/ru/abstractmenus/menu/AbstractMenu.java +++ b/plugin/src/main/java/ru/abstractmenus/menu/AbstractMenu.java @@ -13,7 +13,7 @@ import org.jetbrains.annotations.NotNull; import ru.abstractmenus.AbstractMenus; import ru.abstractmenus.MainConfig; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.Rule; import ru.abstractmenus.datatype.TypeSlot; import ru.abstractmenus.menu.item.MenuItem; @@ -358,7 +358,7 @@ protected int getFreeSlot() { } protected void createInventory(Player player, InventoryHolder holder) { - String title = Handlers.getPlaceholderHandler().replace(player, this.title); + String title = AbstractMenusApi.get().providers().placeholders().replace(player, this.title); title = MiniMessageUtil.parseToLegacy(title); if (this.type != null) { diff --git a/src/main/java/ru/abstractmenus/menu/MenuListener.java b/plugin/src/main/java/ru/abstractmenus/menu/MenuListener.java similarity index 100% rename from src/main/java/ru/abstractmenus/menu/MenuListener.java rename to plugin/src/main/java/ru/abstractmenus/menu/MenuListener.java diff --git a/src/main/java/ru/abstractmenus/menu/SimpleMenu.java b/plugin/src/main/java/ru/abstractmenus/menu/SimpleMenu.java similarity index 100% rename from src/main/java/ru/abstractmenus/menu/SimpleMenu.java rename to plugin/src/main/java/ru/abstractmenus/menu/SimpleMenu.java diff --git a/src/main/java/ru/abstractmenus/menu/animated/AnimatedMenu.java b/plugin/src/main/java/ru/abstractmenus/menu/animated/AnimatedMenu.java similarity index 100% rename from src/main/java/ru/abstractmenus/menu/animated/AnimatedMenu.java rename to plugin/src/main/java/ru/abstractmenus/menu/animated/AnimatedMenu.java diff --git a/src/main/java/ru/abstractmenus/menu/animated/Frame.java b/plugin/src/main/java/ru/abstractmenus/menu/animated/Frame.java similarity index 100% rename from src/main/java/ru/abstractmenus/menu/animated/Frame.java rename to plugin/src/main/java/ru/abstractmenus/menu/animated/Frame.java diff --git a/src/main/java/ru/abstractmenus/menu/generated/GeneratedMenu.java b/plugin/src/main/java/ru/abstractmenus/menu/generated/GeneratedMenu.java similarity index 100% rename from src/main/java/ru/abstractmenus/menu/generated/GeneratedMenu.java rename to plugin/src/main/java/ru/abstractmenus/menu/generated/GeneratedMenu.java diff --git a/src/main/java/ru/abstractmenus/menu/generated/Matrix.java b/plugin/src/main/java/ru/abstractmenus/menu/generated/Matrix.java similarity index 100% rename from src/main/java/ru/abstractmenus/menu/generated/Matrix.java rename to plugin/src/main/java/ru/abstractmenus/menu/generated/Matrix.java diff --git a/src/main/java/ru/abstractmenus/menu/item/InventoryItem.java b/plugin/src/main/java/ru/abstractmenus/menu/item/InventoryItem.java similarity index 100% rename from src/main/java/ru/abstractmenus/menu/item/InventoryItem.java rename to plugin/src/main/java/ru/abstractmenus/menu/item/InventoryItem.java diff --git a/src/main/java/ru/abstractmenus/menu/item/MenuItem.java b/plugin/src/main/java/ru/abstractmenus/menu/item/MenuItem.java similarity index 100% rename from src/main/java/ru/abstractmenus/menu/item/MenuItem.java rename to plugin/src/main/java/ru/abstractmenus/menu/item/MenuItem.java diff --git a/src/main/java/ru/abstractmenus/menu/item/SimpleItem.java b/plugin/src/main/java/ru/abstractmenus/menu/item/SimpleItem.java similarity index 100% rename from src/main/java/ru/abstractmenus/menu/item/SimpleItem.java rename to plugin/src/main/java/ru/abstractmenus/menu/item/SimpleItem.java diff --git a/src/main/java/ru/abstractmenus/nms/actionbar/ActionBar.java b/plugin/src/main/java/ru/abstractmenus/nms/actionbar/ActionBar.java similarity index 100% rename from src/main/java/ru/abstractmenus/nms/actionbar/ActionBar.java rename to plugin/src/main/java/ru/abstractmenus/nms/actionbar/ActionBar.java diff --git a/src/main/java/ru/abstractmenus/nms/actionbar/ActionBar_1_9.java b/plugin/src/main/java/ru/abstractmenus/nms/actionbar/ActionBar_1_9.java similarity index 100% rename from src/main/java/ru/abstractmenus/nms/actionbar/ActionBar_1_9.java rename to plugin/src/main/java/ru/abstractmenus/nms/actionbar/ActionBar_1_9.java diff --git a/src/main/java/ru/abstractmenus/nms/actionbar/ActionBar_PaperMc.java b/plugin/src/main/java/ru/abstractmenus/nms/actionbar/ActionBar_PaperMc.java similarity index 100% rename from src/main/java/ru/abstractmenus/nms/actionbar/ActionBar_PaperMc.java rename to plugin/src/main/java/ru/abstractmenus/nms/actionbar/ActionBar_PaperMc.java diff --git a/src/main/java/ru/abstractmenus/nms/book/Book.java b/plugin/src/main/java/ru/abstractmenus/nms/book/Book.java similarity index 100% rename from src/main/java/ru/abstractmenus/nms/book/Book.java rename to plugin/src/main/java/ru/abstractmenus/nms/book/Book.java diff --git a/src/main/java/ru/abstractmenus/nms/book/Book_1_15.java b/plugin/src/main/java/ru/abstractmenus/nms/book/Book_1_15.java similarity index 100% rename from src/main/java/ru/abstractmenus/nms/book/Book_1_15.java rename to plugin/src/main/java/ru/abstractmenus/nms/book/Book_1_15.java diff --git a/src/main/java/ru/abstractmenus/nms/title/SenderModern.java b/plugin/src/main/java/ru/abstractmenus/nms/title/SenderModern.java similarity index 100% rename from src/main/java/ru/abstractmenus/nms/title/SenderModern.java rename to plugin/src/main/java/ru/abstractmenus/nms/title/SenderModern.java diff --git a/src/main/java/ru/abstractmenus/nms/title/Title.java b/plugin/src/main/java/ru/abstractmenus/nms/title/Title.java similarity index 100% rename from src/main/java/ru/abstractmenus/nms/title/Title.java rename to plugin/src/main/java/ru/abstractmenus/nms/title/Title.java diff --git a/src/main/java/ru/abstractmenus/nms/title/TitleSender.java b/plugin/src/main/java/ru/abstractmenus/nms/title/TitleSender.java similarity index 100% rename from src/main/java/ru/abstractmenus/nms/title/TitleSender.java rename to plugin/src/main/java/ru/abstractmenus/nms/title/TitleSender.java diff --git a/src/main/java/ru/abstractmenus/placeholders/PAPIPlaceholders.java b/plugin/src/main/java/ru/abstractmenus/placeholders/PAPIPlaceholders.java similarity index 100% rename from src/main/java/ru/abstractmenus/placeholders/PAPIPlaceholders.java rename to plugin/src/main/java/ru/abstractmenus/placeholders/PAPIPlaceholders.java diff --git a/src/main/java/ru/abstractmenus/placeholders/PlaceholderHook.java b/plugin/src/main/java/ru/abstractmenus/placeholders/PlaceholderHook.java similarity index 100% rename from src/main/java/ru/abstractmenus/placeholders/PlaceholderHook.java rename to plugin/src/main/java/ru/abstractmenus/placeholders/PlaceholderHook.java diff --git a/src/main/java/ru/abstractmenus/placeholders/hooks/ActivatorPlaceholders.java b/plugin/src/main/java/ru/abstractmenus/placeholders/hooks/ActivatorPlaceholders.java similarity index 88% rename from src/main/java/ru/abstractmenus/placeholders/hooks/ActivatorPlaceholders.java rename to plugin/src/main/java/ru/abstractmenus/placeholders/hooks/ActivatorPlaceholders.java index 6416433..a6c3893 100644 --- a/src/main/java/ru/abstractmenus/placeholders/hooks/ActivatorPlaceholders.java +++ b/plugin/src/main/java/ru/abstractmenus/placeholders/hooks/ActivatorPlaceholders.java @@ -1,8 +1,8 @@ package ru.abstractmenus.placeholders.hooks; import org.bukkit.entity.Player; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.Activator; -import ru.abstractmenus.api.Types; import ru.abstractmenus.api.ValueExtractor; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.placeholders.PlaceholderHook; @@ -18,7 +18,7 @@ public String replace(String placeholder, Player player) { Activator activator = menu.getActivatedBy().get(); if (placeholder.equalsIgnoreCase("name")) - return Types.getActivatorName(activator.getClass()); + return AbstractMenusApi.get().activators().name(activator.getClass()); if (menu.getContext().isPresent()) { ValueExtractor extractor = activator.getValueExtractor(); diff --git a/src/main/java/ru/abstractmenus/placeholders/hooks/BungeePlaceholders.java b/plugin/src/main/java/ru/abstractmenus/placeholders/hooks/BungeePlaceholders.java similarity index 100% rename from src/main/java/ru/abstractmenus/placeholders/hooks/BungeePlaceholders.java rename to plugin/src/main/java/ru/abstractmenus/placeholders/hooks/BungeePlaceholders.java diff --git a/src/main/java/ru/abstractmenus/placeholders/hooks/CatalogPlaceholders.java b/plugin/src/main/java/ru/abstractmenus/placeholders/hooks/CatalogPlaceholders.java similarity index 100% rename from src/main/java/ru/abstractmenus/placeholders/hooks/CatalogPlaceholders.java rename to plugin/src/main/java/ru/abstractmenus/placeholders/hooks/CatalogPlaceholders.java diff --git a/src/main/java/ru/abstractmenus/placeholders/hooks/HeadAnimPlaceholders.java b/plugin/src/main/java/ru/abstractmenus/placeholders/hooks/HeadAnimPlaceholders.java similarity index 100% rename from src/main/java/ru/abstractmenus/placeholders/hooks/HeadAnimPlaceholders.java rename to plugin/src/main/java/ru/abstractmenus/placeholders/hooks/HeadAnimPlaceholders.java diff --git a/src/main/java/ru/abstractmenus/placeholders/hooks/PlayerPlaceholders.java b/plugin/src/main/java/ru/abstractmenus/placeholders/hooks/PlayerPlaceholders.java similarity index 100% rename from src/main/java/ru/abstractmenus/placeholders/hooks/PlayerPlaceholders.java rename to plugin/src/main/java/ru/abstractmenus/placeholders/hooks/PlayerPlaceholders.java diff --git a/src/main/java/ru/abstractmenus/placeholders/hooks/ServerPlaceholders.java b/plugin/src/main/java/ru/abstractmenus/placeholders/hooks/ServerPlaceholders.java similarity index 100% rename from src/main/java/ru/abstractmenus/placeholders/hooks/ServerPlaceholders.java rename to plugin/src/main/java/ru/abstractmenus/placeholders/hooks/ServerPlaceholders.java diff --git a/src/main/java/ru/abstractmenus/placeholders/hooks/VarPlaceholders.java b/plugin/src/main/java/ru/abstractmenus/placeholders/hooks/VarPlaceholders.java similarity index 100% rename from src/main/java/ru/abstractmenus/placeholders/hooks/VarPlaceholders.java rename to plugin/src/main/java/ru/abstractmenus/placeholders/hooks/VarPlaceholders.java diff --git a/src/main/java/ru/abstractmenus/placeholders/hooks/dnd/ChangedItemPlaceholders.java b/plugin/src/main/java/ru/abstractmenus/placeholders/hooks/dnd/ChangedItemPlaceholders.java similarity index 100% rename from src/main/java/ru/abstractmenus/placeholders/hooks/dnd/ChangedItemPlaceholders.java rename to plugin/src/main/java/ru/abstractmenus/placeholders/hooks/dnd/ChangedItemPlaceholders.java diff --git a/src/main/java/ru/abstractmenus/placeholders/hooks/dnd/PlacedItemPlaceholders.java b/plugin/src/main/java/ru/abstractmenus/placeholders/hooks/dnd/PlacedItemPlaceholders.java similarity index 100% rename from src/main/java/ru/abstractmenus/placeholders/hooks/dnd/PlacedItemPlaceholders.java rename to plugin/src/main/java/ru/abstractmenus/placeholders/hooks/dnd/PlacedItemPlaceholders.java diff --git a/src/main/java/ru/abstractmenus/placeholders/hooks/dnd/TakenItemPlaceholders.java b/plugin/src/main/java/ru/abstractmenus/placeholders/hooks/dnd/TakenItemPlaceholders.java similarity index 100% rename from src/main/java/ru/abstractmenus/placeholders/hooks/dnd/TakenItemPlaceholders.java rename to plugin/src/main/java/ru/abstractmenus/placeholders/hooks/dnd/TakenItemPlaceholders.java diff --git a/src/main/java/ru/abstractmenus/serializers/BannerPatternSerializer.java b/plugin/src/main/java/ru/abstractmenus/serializers/BannerPatternSerializer.java similarity index 100% rename from src/main/java/ru/abstractmenus/serializers/BannerPatternSerializer.java rename to plugin/src/main/java/ru/abstractmenus/serializers/BannerPatternSerializer.java diff --git a/src/main/java/ru/abstractmenus/serializers/ColorSerializer.java b/plugin/src/main/java/ru/abstractmenus/serializers/ColorSerializer.java similarity index 100% rename from src/main/java/ru/abstractmenus/serializers/ColorSerializer.java rename to plugin/src/main/java/ru/abstractmenus/serializers/ColorSerializer.java diff --git a/src/main/java/ru/abstractmenus/serializers/EntityTypeSerializer.java b/plugin/src/main/java/ru/abstractmenus/serializers/EntityTypeSerializer.java similarity index 100% rename from src/main/java/ru/abstractmenus/serializers/EntityTypeSerializer.java rename to plugin/src/main/java/ru/abstractmenus/serializers/EntityTypeSerializer.java diff --git a/src/main/java/ru/abstractmenus/serializers/FireworkEffectSerializer.java b/plugin/src/main/java/ru/abstractmenus/serializers/FireworkEffectSerializer.java similarity index 100% rename from src/main/java/ru/abstractmenus/serializers/FireworkEffectSerializer.java rename to plugin/src/main/java/ru/abstractmenus/serializers/FireworkEffectSerializer.java diff --git a/src/main/java/ru/abstractmenus/serializers/ItemSerializer.java b/plugin/src/main/java/ru/abstractmenus/serializers/ItemSerializer.java similarity index 96% rename from src/main/java/ru/abstractmenus/serializers/ItemSerializer.java rename to plugin/src/main/java/ru/abstractmenus/serializers/ItemSerializer.java index 2c40e9d..2e61c41 100644 --- a/src/main/java/ru/abstractmenus/serializers/ItemSerializer.java +++ b/plugin/src/main/java/ru/abstractmenus/serializers/ItemSerializer.java @@ -15,7 +15,7 @@ import ru.abstractmenus.menu.item.InventoryItem; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.ItemProperty; -import ru.abstractmenus.api.Types; +import ru.abstractmenus.api.AbstractMenusApi; import java.util.*; @@ -67,7 +67,7 @@ public Item deserialize(Class type, ConfigNode node) throws NodeSerializeE for (Map.Entry entry : children.entrySet()) { String key = entry.getKey(); - Class token = Types.getItemPropertyType(key); + Class token = AbstractMenusApi.get().itemProperties().get(key); if (token != null) { item.addProperty(key, entry.getValue().getValue(token)); diff --git a/src/main/java/ru/abstractmenus/serializers/JsonSerializer.java b/plugin/src/main/java/ru/abstractmenus/serializers/JsonSerializer.java similarity index 100% rename from src/main/java/ru/abstractmenus/serializers/JsonSerializer.java rename to plugin/src/main/java/ru/abstractmenus/serializers/JsonSerializer.java diff --git a/src/main/java/ru/abstractmenus/serializers/LocationSerializer.java b/plugin/src/main/java/ru/abstractmenus/serializers/LocationSerializer.java similarity index 100% rename from src/main/java/ru/abstractmenus/serializers/LocationSerializer.java rename to plugin/src/main/java/ru/abstractmenus/serializers/LocationSerializer.java diff --git a/src/main/java/ru/abstractmenus/serializers/NbtCompoundSerializer.java b/plugin/src/main/java/ru/abstractmenus/serializers/NbtCompoundSerializer.java similarity index 100% rename from src/main/java/ru/abstractmenus/serializers/NbtCompoundSerializer.java rename to plugin/src/main/java/ru/abstractmenus/serializers/NbtCompoundSerializer.java diff --git a/src/main/java/ru/abstractmenus/serializers/PotionEffectSerializer.java b/plugin/src/main/java/ru/abstractmenus/serializers/PotionEffectSerializer.java similarity index 100% rename from src/main/java/ru/abstractmenus/serializers/PotionEffectSerializer.java rename to plugin/src/main/java/ru/abstractmenus/serializers/PotionEffectSerializer.java diff --git a/src/main/java/ru/abstractmenus/serializers/Serializers.java b/plugin/src/main/java/ru/abstractmenus/serializers/Serializers.java similarity index 97% rename from src/main/java/ru/abstractmenus/serializers/Serializers.java rename to plugin/src/main/java/ru/abstractmenus/serializers/Serializers.java index 20026d0..a5e6674 100644 --- a/src/main/java/ru/abstractmenus/serializers/Serializers.java +++ b/plugin/src/main/java/ru/abstractmenus/serializers/Serializers.java @@ -7,7 +7,7 @@ import org.bukkit.inventory.ShapedRecipe; import org.bukkit.plugin.Plugin; import org.bukkit.potion.PotionEffect; -import ru.abstractmenus.api.Types; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.command.args.*; import ru.abstractmenus.data.Actions; import ru.abstractmenus.data.BannerData; @@ -39,7 +39,7 @@ private Serializers() { } public static void init(Plugin plugin) { - NodeSerializers serializers = Types.serializers(); + NodeSerializers serializers = AbstractMenusApi.get().serializers(); serializers.register(TypeBool.class, new TypeBool.Serializer()); serializers.register(TypeByte.class, new TypeByte.Serializer()); diff --git a/src/main/java/ru/abstractmenus/serializers/ShapedRecipeSerializer.java b/plugin/src/main/java/ru/abstractmenus/serializers/ShapedRecipeSerializer.java similarity index 100% rename from src/main/java/ru/abstractmenus/serializers/ShapedRecipeSerializer.java rename to plugin/src/main/java/ru/abstractmenus/serializers/ShapedRecipeSerializer.java diff --git a/src/main/java/ru/abstractmenus/serializers/WorldSerializer.java b/plugin/src/main/java/ru/abstractmenus/serializers/WorldSerializer.java similarity index 100% rename from src/main/java/ru/abstractmenus/serializers/WorldSerializer.java rename to plugin/src/main/java/ru/abstractmenus/serializers/WorldSerializer.java diff --git a/src/main/java/ru/abstractmenus/serializers/menu/AnimatedMenuSerializer.java b/plugin/src/main/java/ru/abstractmenus/serializers/menu/AnimatedMenuSerializer.java similarity index 100% rename from src/main/java/ru/abstractmenus/serializers/menu/AnimatedMenuSerializer.java rename to plugin/src/main/java/ru/abstractmenus/serializers/menu/AnimatedMenuSerializer.java diff --git a/src/main/java/ru/abstractmenus/serializers/menu/BaseMenuSerializer.java b/plugin/src/main/java/ru/abstractmenus/serializers/menu/BaseMenuSerializer.java similarity index 97% rename from src/main/java/ru/abstractmenus/serializers/menu/BaseMenuSerializer.java rename to plugin/src/main/java/ru/abstractmenus/serializers/menu/BaseMenuSerializer.java index eeec369..f9ba1ec 100644 --- a/src/main/java/ru/abstractmenus/serializers/menu/BaseMenuSerializer.java +++ b/plugin/src/main/java/ru/abstractmenus/serializers/menu/BaseMenuSerializer.java @@ -8,9 +8,9 @@ import ru.abstractmenus.hocon.api.ConfigNode; import ru.abstractmenus.hocon.api.serialize.NodeSerializeException; import ru.abstractmenus.hocon.api.serialize.NodeSerializer; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.Activator; import ru.abstractmenus.api.inventory.Menu; -import ru.abstractmenus.api.Types; import ru.abstractmenus.menu.AbstractMenu; import ru.abstractmenus.menu.SimpleMenu; import ru.abstractmenus.menu.animated.AnimatedMenu; @@ -130,7 +130,7 @@ private List getActivators(ConfigNode node) throws NodeSerializeExcep for (Map.Entry entry : activators.entrySet()) { String key = entry.getKey(); - Class type = Types.getActivator(key); + Class type = AbstractMenusApi.get().activators().get(key); if (type != null) { Activator activator = entry.getValue().getValue(type); diff --git a/src/main/java/ru/abstractmenus/serializers/menu/GeneratedMenuSerializer.java b/plugin/src/main/java/ru/abstractmenus/serializers/menu/GeneratedMenuSerializer.java similarity index 89% rename from src/main/java/ru/abstractmenus/serializers/menu/GeneratedMenuSerializer.java rename to plugin/src/main/java/ru/abstractmenus/serializers/menu/GeneratedMenuSerializer.java index 36b54a5..4e0b7f5 100644 --- a/src/main/java/ru/abstractmenus/serializers/menu/GeneratedMenuSerializer.java +++ b/plugin/src/main/java/ru/abstractmenus/serializers/menu/GeneratedMenuSerializer.java @@ -1,11 +1,11 @@ package ru.abstractmenus.serializers.menu; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.Catalog; import ru.abstractmenus.hocon.api.ConfigNode; import ru.abstractmenus.hocon.api.serialize.NodeSerializeException; import ru.abstractmenus.menu.generated.GeneratedMenu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Types; import ru.abstractmenus.menu.generated.Matrix; public class GeneratedMenuSerializer implements MenuSerializer { @@ -15,7 +15,7 @@ public GeneratedMenu deserialize(ConfigNode node, String title, int size) throws GeneratedMenu menu = new GeneratedMenu(title, size); String catalogType = node.node("catalog", "type").getString(); - Class> catalogToken = Types.getCatalogType(catalogType); + Class> catalogToken = AbstractMenusApi.get().catalogs().get(catalogType); if (catalogToken != null) { menu.setCatalog(node.node("catalog").getValue(catalogToken)); diff --git a/src/main/java/ru/abstractmenus/serializers/menu/MenuSerializer.java b/plugin/src/main/java/ru/abstractmenus/serializers/menu/MenuSerializer.java similarity index 100% rename from src/main/java/ru/abstractmenus/serializers/menu/MenuSerializer.java rename to plugin/src/main/java/ru/abstractmenus/serializers/menu/MenuSerializer.java diff --git a/src/main/java/ru/abstractmenus/serializers/menu/SimpleMenuSerializer.java b/plugin/src/main/java/ru/abstractmenus/serializers/menu/SimpleMenuSerializer.java similarity index 100% rename from src/main/java/ru/abstractmenus/serializers/menu/SimpleMenuSerializer.java rename to plugin/src/main/java/ru/abstractmenus/serializers/menu/SimpleMenuSerializer.java diff --git a/src/main/java/ru/abstractmenus/services/BungeeManager.java b/plugin/src/main/java/ru/abstractmenus/services/BungeeManager.java similarity index 100% rename from src/main/java/ru/abstractmenus/services/BungeeManager.java rename to plugin/src/main/java/ru/abstractmenus/services/BungeeManager.java diff --git a/src/main/java/ru/abstractmenus/services/HeadAnimManager.java b/plugin/src/main/java/ru/abstractmenus/services/HeadAnimManager.java similarity index 100% rename from src/main/java/ru/abstractmenus/services/HeadAnimManager.java rename to plugin/src/main/java/ru/abstractmenus/services/HeadAnimManager.java diff --git a/src/main/java/ru/abstractmenus/services/MenuManager.java b/plugin/src/main/java/ru/abstractmenus/services/MenuManager.java similarity index 98% rename from src/main/java/ru/abstractmenus/services/MenuManager.java rename to plugin/src/main/java/ru/abstractmenus/services/MenuManager.java index d55a78a..b8d9d50 100644 --- a/src/main/java/ru/abstractmenus/services/MenuManager.java +++ b/plugin/src/main/java/ru/abstractmenus/services/MenuManager.java @@ -6,9 +6,9 @@ import org.bukkit.plugin.Plugin; import ru.abstractmenus.AbstractMenus; import ru.abstractmenus.MainConfig; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.Activator; import ru.abstractmenus.api.inventory.Menu; -import ru.abstractmenus.api.Types; import ru.abstractmenus.command.Command; import ru.abstractmenus.data.actions.ActionInputChat; import ru.abstractmenus.data.activators.OpenCommand; @@ -212,7 +212,7 @@ private int loadFile(Path file) { try { conf = ConfigurationLoader.builder() .source(ConfigSources.path(file)) - .serializers(Types.serializers()) + .serializers(AbstractMenusApi.get().serializers()) .build() .load(); } catch (Throwable t) { @@ -364,7 +364,7 @@ private void loadExampleMenus() throws Exception { ConfigurationLoader.builder() .source(ConfigSources.resource("/menu.conf", plugin) .copyTo(menuFolder)) - .serializers(Types.serializers()) + .serializers(AbstractMenusApi.get().serializers()) .build() .load(); @@ -381,7 +381,7 @@ private void loadExampleMenus() throws Exception { ConfigurationLoader.builder() .source(ConfigSources.resource(animMenuPath, plugin) .copyTo(menuFolder)) - .serializers(Types.serializers()) + .serializers(AbstractMenusApi.get().serializers()) .build() .load(); } diff --git a/src/main/java/ru/abstractmenus/services/ProfileStorage.java b/plugin/src/main/java/ru/abstractmenus/services/ProfileStorage.java similarity index 100% rename from src/main/java/ru/abstractmenus/services/ProfileStorage.java rename to plugin/src/main/java/ru/abstractmenus/services/ProfileStorage.java diff --git a/src/main/java/ru/abstractmenus/services/proxy/dto/ChangeSkinDTO.java b/plugin/src/main/java/ru/abstractmenus/services/proxy/dto/ChangeSkinDTO.java similarity index 100% rename from src/main/java/ru/abstractmenus/services/proxy/dto/ChangeSkinDTO.java rename to plugin/src/main/java/ru/abstractmenus/services/proxy/dto/ChangeSkinDTO.java diff --git a/src/main/java/ru/abstractmenus/util/ArrayListIterator.java b/plugin/src/main/java/ru/abstractmenus/util/ArrayListIterator.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/ArrayListIterator.java rename to plugin/src/main/java/ru/abstractmenus/util/ArrayListIterator.java diff --git a/src/main/java/ru/abstractmenus/util/FileUtils.java b/plugin/src/main/java/ru/abstractmenus/util/FileUtils.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/FileUtils.java rename to plugin/src/main/java/ru/abstractmenus/util/FileUtils.java diff --git a/src/main/java/ru/abstractmenus/util/LegacyColorTagReplacer.java b/plugin/src/main/java/ru/abstractmenus/util/LegacyColorTagReplacer.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/LegacyColorTagReplacer.java rename to plugin/src/main/java/ru/abstractmenus/util/LegacyColorTagReplacer.java diff --git a/src/main/java/ru/abstractmenus/util/MiniMessageUtil.java b/plugin/src/main/java/ru/abstractmenus/util/MiniMessageUtil.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/MiniMessageUtil.java rename to plugin/src/main/java/ru/abstractmenus/util/MiniMessageUtil.java diff --git a/src/main/java/ru/abstractmenus/util/NMS.java b/plugin/src/main/java/ru/abstractmenus/util/NMS.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/NMS.java rename to plugin/src/main/java/ru/abstractmenus/util/NMS.java diff --git a/src/main/java/ru/abstractmenus/util/NumberUtil.java b/plugin/src/main/java/ru/abstractmenus/util/NumberUtil.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/NumberUtil.java rename to plugin/src/main/java/ru/abstractmenus/util/NumberUtil.java diff --git a/src/main/java/ru/abstractmenus/util/RegionUtils.java b/plugin/src/main/java/ru/abstractmenus/util/RegionUtils.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/RegionUtils.java rename to plugin/src/main/java/ru/abstractmenus/util/RegionUtils.java diff --git a/src/main/java/ru/abstractmenus/util/SlotUtil.java b/plugin/src/main/java/ru/abstractmenus/util/SlotUtil.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/SlotUtil.java rename to plugin/src/main/java/ru/abstractmenus/util/SlotUtil.java diff --git a/src/main/java/ru/abstractmenus/util/StringUtil.java b/plugin/src/main/java/ru/abstractmenus/util/StringUtil.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/StringUtil.java rename to plugin/src/main/java/ru/abstractmenus/util/StringUtil.java diff --git a/src/main/java/ru/abstractmenus/util/TimeParser.java b/plugin/src/main/java/ru/abstractmenus/util/TimeParser.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/TimeParser.java rename to plugin/src/main/java/ru/abstractmenus/util/TimeParser.java diff --git a/src/main/java/ru/abstractmenus/util/TimeUtil.java b/plugin/src/main/java/ru/abstractmenus/util/TimeUtil.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/TimeUtil.java rename to plugin/src/main/java/ru/abstractmenus/util/TimeUtil.java diff --git a/src/main/java/ru/abstractmenus/util/Tuple.java b/plugin/src/main/java/ru/abstractmenus/util/Tuple.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/Tuple.java rename to plugin/src/main/java/ru/abstractmenus/util/Tuple.java diff --git a/src/main/java/ru/abstractmenus/util/bukkit/BukkitTasks.java b/plugin/src/main/java/ru/abstractmenus/util/bukkit/BukkitTasks.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/bukkit/BukkitTasks.java rename to plugin/src/main/java/ru/abstractmenus/util/bukkit/BukkitTasks.java diff --git a/src/main/java/ru/abstractmenus/util/bukkit/Events.java b/plugin/src/main/java/ru/abstractmenus/util/bukkit/Events.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/bukkit/Events.java rename to plugin/src/main/java/ru/abstractmenus/util/bukkit/Events.java diff --git a/src/main/java/ru/abstractmenus/util/bukkit/ItemUtil.java b/plugin/src/main/java/ru/abstractmenus/util/bukkit/ItemUtil.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/bukkit/ItemUtil.java rename to plugin/src/main/java/ru/abstractmenus/util/bukkit/ItemUtil.java diff --git a/src/main/java/ru/abstractmenus/util/bukkit/MojangApi.java b/plugin/src/main/java/ru/abstractmenus/util/bukkit/MojangApi.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/bukkit/MojangApi.java rename to plugin/src/main/java/ru/abstractmenus/util/bukkit/MojangApi.java diff --git a/src/main/java/ru/abstractmenus/util/bukkit/Skulls.java b/plugin/src/main/java/ru/abstractmenus/util/bukkit/Skulls.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/bukkit/Skulls.java rename to plugin/src/main/java/ru/abstractmenus/util/bukkit/Skulls.java diff --git a/src/main/java/ru/abstractmenus/util/bukkit/TaskHandle.java b/plugin/src/main/java/ru/abstractmenus/util/bukkit/TaskHandle.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/bukkit/TaskHandle.java rename to plugin/src/main/java/ru/abstractmenus/util/bukkit/TaskHandle.java diff --git a/src/main/java/ru/abstractmenus/util/bukkit/UuidUtil.java b/plugin/src/main/java/ru/abstractmenus/util/bukkit/UuidUtil.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/bukkit/UuidUtil.java rename to plugin/src/main/java/ru/abstractmenus/util/bukkit/UuidUtil.java diff --git a/src/main/java/ru/abstractmenus/util/proxy/ClassInfo.java b/plugin/src/main/java/ru/abstractmenus/util/proxy/ClassInfo.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/proxy/ClassInfo.java rename to plugin/src/main/java/ru/abstractmenus/util/proxy/ClassInfo.java diff --git a/src/main/java/ru/abstractmenus/util/proxy/ReflectionUtil.java b/plugin/src/main/java/ru/abstractmenus/util/proxy/ReflectionUtil.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/proxy/ReflectionUtil.java rename to plugin/src/main/java/ru/abstractmenus/util/proxy/ReflectionUtil.java diff --git a/src/main/java/ru/abstractmenus/variables/VarBuilderImpl.java b/plugin/src/main/java/ru/abstractmenus/variables/VarBuilderImpl.java similarity index 100% rename from src/main/java/ru/abstractmenus/variables/VarBuilderImpl.java rename to plugin/src/main/java/ru/abstractmenus/variables/VarBuilderImpl.java diff --git a/src/main/java/ru/abstractmenus/variables/VarData.java b/plugin/src/main/java/ru/abstractmenus/variables/VarData.java similarity index 100% rename from src/main/java/ru/abstractmenus/variables/VarData.java rename to plugin/src/main/java/ru/abstractmenus/variables/VarData.java diff --git a/src/main/java/ru/abstractmenus/variables/VarImpl.java b/plugin/src/main/java/ru/abstractmenus/variables/VarImpl.java similarity index 100% rename from src/main/java/ru/abstractmenus/variables/VarImpl.java rename to plugin/src/main/java/ru/abstractmenus/variables/VarImpl.java diff --git a/src/main/java/ru/abstractmenus/variables/VarNumData.java b/plugin/src/main/java/ru/abstractmenus/variables/VarNumData.java similarity index 100% rename from src/main/java/ru/abstractmenus/variables/VarNumData.java rename to plugin/src/main/java/ru/abstractmenus/variables/VarNumData.java diff --git a/src/main/java/ru/abstractmenus/variables/VariableManagerImpl.java b/plugin/src/main/java/ru/abstractmenus/variables/VariableManagerImpl.java similarity index 100% rename from src/main/java/ru/abstractmenus/variables/VariableManagerImpl.java rename to plugin/src/main/java/ru/abstractmenus/variables/VariableManagerImpl.java diff --git a/src/main/java/ru/abstractmenus/variables/VariablesDao.java b/plugin/src/main/java/ru/abstractmenus/variables/VariablesDao.java similarity index 100% rename from src/main/java/ru/abstractmenus/variables/VariablesDao.java rename to plugin/src/main/java/ru/abstractmenus/variables/VariablesDao.java diff --git a/src/main/resources/1_13/menu_anim.conf b/plugin/src/main/resources/1_13/menu_anim.conf similarity index 100% rename from src/main/resources/1_13/menu_anim.conf rename to plugin/src/main/resources/1_13/menu_anim.conf diff --git a/src/main/resources/1_21/menu_anim.conf b/plugin/src/main/resources/1_21/menu_anim.conf similarity index 100% rename from src/main/resources/1_21/menu_anim.conf rename to plugin/src/main/resources/1_21/menu_anim.conf diff --git a/src/main/resources/LICENSES b/plugin/src/main/resources/LICENSES similarity index 100% rename from src/main/resources/LICENSES rename to plugin/src/main/resources/LICENSES diff --git a/src/main/resources/animated_heads.conf b/plugin/src/main/resources/animated_heads.conf similarity index 100% rename from src/main/resources/animated_heads.conf rename to plugin/src/main/resources/animated_heads.conf diff --git a/src/main/resources/bench-menus/README.md b/plugin/src/main/resources/bench-menus/README.md similarity index 100% rename from src/main/resources/bench-menus/README.md rename to plugin/src/main/resources/bench-menus/README.md diff --git a/src/main/resources/bench-menus/_templates.conf b/plugin/src/main/resources/bench-menus/_templates.conf similarity index 100% rename from src/main/resources/bench-menus/_templates.conf rename to plugin/src/main/resources/bench-menus/_templates.conf diff --git a/src/main/resources/bench-menus/bench_actions_rich.conf b/plugin/src/main/resources/bench-menus/bench_actions_rich.conf similarity index 100% rename from src/main/resources/bench-menus/bench_actions_rich.conf rename to plugin/src/main/resources/bench-menus/bench_actions_rich.conf diff --git a/src/main/resources/bench-menus/bench_animated_carousel.conf b/plugin/src/main/resources/bench-menus/bench_animated_carousel.conf similarity index 100% rename from src/main/resources/bench-menus/bench_animated_carousel.conf rename to plugin/src/main/resources/bench-menus/bench_animated_carousel.conf diff --git a/src/main/resources/bench-menus/bench_complex_rules.conf b/plugin/src/main/resources/bench-menus/bench_complex_rules.conf similarity index 100% rename from src/main/resources/bench-menus/bench_complex_rules.conf rename to plugin/src/main/resources/bench-menus/bench_complex_rules.conf diff --git a/src/main/resources/bench-menus/bench_dynamic_lore.conf b/plugin/src/main/resources/bench-menus/bench_dynamic_lore.conf similarity index 100% rename from src/main/resources/bench-menus/bench_dynamic_lore.conf rename to plugin/src/main/resources/bench-menus/bench_dynamic_lore.conf diff --git a/src/main/resources/bench-menus/bench_generated_pagination.conf b/plugin/src/main/resources/bench-menus/bench_generated_pagination.conf similarity index 100% rename from src/main/resources/bench-menus/bench_generated_pagination.conf rename to plugin/src/main/resources/bench-menus/bench_generated_pagination.conf diff --git a/src/main/resources/bench-menus/bench_high_refresh.conf b/plugin/src/main/resources/bench-menus/bench_high_refresh.conf similarity index 100% rename from src/main/resources/bench-menus/bench_high_refresh.conf rename to plugin/src/main/resources/bench-menus/bench_high_refresh.conf diff --git a/src/main/resources/bench-menus/bench_minimessage_heavy.conf b/plugin/src/main/resources/bench-menus/bench_minimessage_heavy.conf similarity index 100% rename from src/main/resources/bench-menus/bench_minimessage_heavy.conf rename to plugin/src/main/resources/bench-menus/bench_minimessage_heavy.conf diff --git a/src/main/resources/bench-menus/bench_skull_grid.conf b/plugin/src/main/resources/bench-menus/bench_skull_grid.conf similarity index 100% rename from src/main/resources/bench-menus/bench_skull_grid.conf rename to plugin/src/main/resources/bench-menus/bench_skull_grid.conf diff --git a/src/main/resources/bench-menus/bench_static_grid.conf b/plugin/src/main/resources/bench-menus/bench_static_grid.conf similarity index 100% rename from src/main/resources/bench-menus/bench_static_grid.conf rename to plugin/src/main/resources/bench-menus/bench_static_grid.conf diff --git a/src/main/resources/bench-menus/bench_variables.conf b/plugin/src/main/resources/bench-menus/bench_variables.conf similarity index 100% rename from src/main/resources/bench-menus/bench_variables.conf rename to plugin/src/main/resources/bench-menus/bench_variables.conf diff --git a/src/main/resources/config.conf b/plugin/src/main/resources/config.conf similarity index 84% rename from src/main/resources/config.conf rename to plugin/src/main/resources/config.conf index a312c2c..847bb56 100644 --- a/src/main/resources/config.conf +++ b/plugin/src/main/resources/config.conf @@ -39,6 +39,18 @@ time { second: "sec" } +# Default provider selection. Values: an explicit provider id (e.g. +# "vault", "playerpoints") or "auto" to use priority-based resolution +# (highest-priority registered provider wins). Per-action overrides in +# menus (via `provider: "..."`) always take precedence over this default. +providers { + economy = "auto" + permissions = "auto" + levels = "auto" + placeholders = "auto" + skins = "auto" +} + # Click debounce floors (milliseconds). Active when a menu item has # clickCooldown > 0 (default = 1 tick); the user's clickCooldown wins # only when it's larger than the floor. Set clickCooldown:0 on an item diff --git a/src/main/resources/menu.conf b/plugin/src/main/resources/menu.conf similarity index 100% rename from src/main/resources/menu.conf rename to plugin/src/main/resources/menu.conf diff --git a/src/main/resources/menu_anim.conf b/plugin/src/main/resources/menu_anim.conf similarity index 100% rename from src/main/resources/menu_anim.conf rename to plugin/src/main/resources/menu_anim.conf diff --git a/src/main/resources/plugin.yml b/plugin/src/main/resources/plugin.yml similarity index 100% rename from src/main/resources/plugin.yml rename to plugin/src/main/resources/plugin.yml diff --git a/src/main/resources/variables.sql b/plugin/src/main/resources/variables.sql similarity index 100% rename from src/main/resources/variables.sql rename to plugin/src/main/resources/variables.sql diff --git a/plugin/src/test/java/ru/abstractmenus/addon/AddonClassLoaderTest.java b/plugin/src/test/java/ru/abstractmenus/addon/AddonClassLoaderTest.java new file mode 100644 index 0000000..9acb59c --- /dev/null +++ b/plugin/src/test/java/ru/abstractmenus/addon/AddonClassLoaderTest.java @@ -0,0 +1,100 @@ +package ru.abstractmenus.addon; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import ru.abstractmenus.api.MenuExtension; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.net.URL; +import java.nio.file.Path; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; + +import static org.junit.jupiter.api.Assertions.*; + +class AddonClassLoaderTest { + + @Test + void parentFirstForApiPackage(@TempDir Path tmp) throws Exception { + // A jar that SHIPS its own copy of a class in ru.abstractmenus.api.*. + // The classloader must still prefer the parent's copy. + File jar = tmp.resolve("addon.jar").toFile(); + try (JarOutputStream out = new JarOutputStream(new FileOutputStream(jar))) { + // Use the real parent class's bytes (we just want the jar to contain + // the path). The key point is: the loader returns the PARENT's + // Class, not a freshly-defined one from the jar. + byte[] bytes = classBytes(MenuExtension.class); + out.putNextEntry(new JarEntry("ru/abstractmenus/api/MenuExtension.class")); + out.write(bytes); + out.closeEntry(); + } + + try (AddonClassLoader cl = new AddonClassLoader( + new URL[]{jar.toURI().toURL()}, + AddonClassLoaderTest.class.getClassLoader())) { + + Class loaded = cl.loadClass("ru.abstractmenus.api.MenuExtension"); + assertSame(MenuExtension.class, loaded, + "parent-first must return the parent's Class object"); + } + } + + @Test + void childFirstForEverythingElse(@TempDir Path tmp) throws Exception { + // SampleAddonClass is a static inner class; its binary name contains '$'. + String binaryName = SampleAddonClass.class.getName(); // ru.abstractmenus.addon.AddonClassLoaderTest$SampleAddonClass + String entryPath = binaryName.replace('.', '/') + ".class"; + + File jar = tmp.resolve("addon.jar").toFile(); + try (JarOutputStream out = new JarOutputStream(new FileOutputStream(jar))) { + byte[] bytes = classBytes(SampleAddonClass.class); + out.putNextEntry(new JarEntry(entryPath)); + out.write(bytes); + out.closeEntry(); + } + + try (AddonClassLoader cl = new AddonClassLoader( + new URL[]{jar.toURI().toURL()}, + AddonClassLoaderTest.class.getClassLoader())) { + + Class loaded = cl.loadClass(binaryName); + assertEquals(binaryName, loaded.getName()); + } + } + + @Test + void missingClass_throwsClassNotFound(@TempDir Path tmp) throws Exception { + File jar = tmp.resolve("empty.jar").toFile(); + try (JarOutputStream out = new JarOutputStream(new FileOutputStream(jar))) { + // empty jar + } + + try (AddonClassLoader cl = new AddonClassLoader( + new URL[]{jar.toURI().toURL()}, + AddonClassLoaderTest.class.getClassLoader())) { + + assertThrows(ClassNotFoundException.class, + () -> cl.loadClass("com.nowhere.Missing")); + } + } + + // --- helpers --- + + private static byte[] classBytes(Class cls) throws Exception { + String path = cls.getName().replace('.', '/') + ".class"; + try (InputStream in = cls.getClassLoader().getResourceAsStream(path); + ByteArrayOutputStream buf = new ByteArrayOutputStream()) { + if (in == null) throw new IllegalStateException("Cannot find " + path); + in.transferTo(buf); + return buf.toByteArray(); + } + } + + /** Public fixture class — a test jar will pack its bytecode. */ + public static class SampleAddonClass { + public String ping() { return "pong"; } + } +} diff --git a/plugin/src/test/java/ru/abstractmenus/addon/AddonConfTest.java b/plugin/src/test/java/ru/abstractmenus/addon/AddonConfTest.java new file mode 100644 index 0000000..d8b04af --- /dev/null +++ b/plugin/src/test/java/ru/abstractmenus/addon/AddonConfTest.java @@ -0,0 +1,111 @@ +package ru.abstractmenus.addon; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class AddonConfTest { + + @Test + void parse_minimal_requiredOnly() { + String hocon = """ + name = "MyAddon" + version = "1.0.0" + main = "com.example.MyAddon" + """; + AddonConf c = AddonConf.parse(hocon); + + assertEquals("MyAddon", c.name()); + assertEquals("1.0.0", c.version()); + assertEquals("com.example.MyAddon", c.main()); + assertEquals(List.of(), c.authors()); + assertEquals("", c.description()); + assertNull(c.targetApiVersion()); + assertEquals(List.of(), c.addonDependencies()); + assertEquals(List.of(), c.pluginDependencies()); + assertEquals(List.of(), c.pluginSoftDependencies()); + } + + @Test + void parse_full() { + String hocon = """ + name = "MyAddon" + version = "1.2.3" + main = "com.example.MyAddon" + authors = ["alice", "bob"] + description = "Does things." + targetApiVersion = "2.0.0" + addonDependencies = ["common-utils"] + pluginDependencies = ["NBT-API"] + pluginSoftDependencies = ["WorldGuard"] + """; + AddonConf c = AddonConf.parse(hocon); + + assertEquals(List.of("alice", "bob"), c.authors()); + assertEquals("Does things.", c.description()); + assertEquals("2.0.0", c.targetApiVersion()); + assertEquals(List.of("common-utils"), c.addonDependencies()); + assertEquals(List.of("NBT-API"), c.pluginDependencies()); + assertEquals(List.of("WorldGuard"), c.pluginSoftDependencies()); + } + + @Test + void parse_missingName_throws() { + String hocon = """ + version = "1.0.0" + main = "com.example.MyAddon" + """; + AddonConfParseException ex = assertThrows(AddonConfParseException.class, + () -> AddonConf.parse(hocon)); + assertTrue(ex.getMessage().toLowerCase().contains("name")); + } + + @Test + void parse_missingVersion_throws() { + String hocon = """ + name = "MyAddon" + main = "com.example.MyAddon" + """; + assertThrows(AddonConfParseException.class, () -> AddonConf.parse(hocon)); + } + + @Test + void parse_missingMain_throws() { + String hocon = """ + name = "MyAddon" + version = "1.0.0" + """; + assertThrows(AddonConfParseException.class, () -> AddonConf.parse(hocon)); + } + + @Test + void parse_blankName_throws() { + String hocon = """ + name = " " + version = "1.0.0" + main = "com.example.MyAddon" + """; + assertThrows(AddonConfParseException.class, () -> AddonConf.parse(hocon)); + } + + @Test + void parse_malformedHocon_throws() { + String hocon = "this is not { valid hocon"; + assertThrows(AddonConfParseException.class, () -> AddonConf.parse(hocon)); + } + + @Test + void listFields_acceptSingleElementInsteadOfList() { + // HOCON allows either a single string or a list; both should parse. + String hocon = """ + name = "MyAddon" + version = "1.0.0" + main = "com.example.MyAddon" + authors = "solo" + """; + AddonConf c = AddonConf.parse(hocon); + assertEquals(List.of("solo"), c.authors()); + } +} diff --git a/plugin/src/test/java/ru/abstractmenus/addon/AddonDependencyGraphTest.java b/plugin/src/test/java/ru/abstractmenus/addon/AddonDependencyGraphTest.java new file mode 100644 index 0000000..a692054 --- /dev/null +++ b/plugin/src/test/java/ru/abstractmenus/addon/AddonDependencyGraphTest.java @@ -0,0 +1,87 @@ +package ru.abstractmenus.addon; + +import org.junit.jupiter.api.Test; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +class AddonDependencyGraphTest { + + @Test + void empty_returnsEmpty() { + List order = AddonDependencyGraph.topoSort(Map.of()); + assertEquals(List.of(), order); + } + + @Test + void noDependencies_preservesInsertionOrder() { + Map> deps = new LinkedHashMap<>(); + deps.put("a", List.of()); + deps.put("b", List.of()); + deps.put("c", List.of()); + + assertEquals(List.of("a", "b", "c"), AddonDependencyGraph.topoSort(deps)); + } + + @Test + void linearChain_sortsDeepToShallow() { + // c depends on b; b depends on a → enable order a, b, c + Map> deps = new LinkedHashMap<>(); + deps.put("c", List.of("b")); + deps.put("b", List.of("a")); + deps.put("a", List.of()); + + assertEquals(List.of("a", "b", "c"), AddonDependencyGraph.topoSort(deps)); + } + + @Test + void diamond_isHandled() { + // a + // / \ + // b c + // \ / + // d + Map> deps = new LinkedHashMap<>(); + deps.put("d", List.of("b", "c")); + deps.put("c", List.of("a")); + deps.put("b", List.of("a")); + deps.put("a", List.of()); + + List order = AddonDependencyGraph.topoSort(deps); + + assertTrue(order.indexOf("a") < order.indexOf("b")); + assertTrue(order.indexOf("a") < order.indexOf("c")); + assertTrue(order.indexOf("b") < order.indexOf("d")); + assertTrue(order.indexOf("c") < order.indexOf("d")); + } + + @Test + void selfCycle_throws() { + Map> deps = Map.of("a", List.of("a")); + AddonDependencyCycleException ex = assertThrows( + AddonDependencyCycleException.class, + () -> AddonDependencyGraph.topoSort(deps)); + assertTrue(ex.getMessage().contains("a")); + } + + @Test + void twoNodeCycle_throws() { + Map> deps = Map.of( + "a", List.of("b"), + "b", List.of("a")); + assertThrows(AddonDependencyCycleException.class, + () -> AddonDependencyGraph.topoSort(deps)); + } + + @Test + void unknownDependency_throws() { + Map> deps = Map.of("a", List.of("ghost")); + AddonDependencyException ex = assertThrows( + AddonDependencyException.class, + () -> AddonDependencyGraph.topoSort(deps)); + assertTrue(ex.getMessage().toLowerCase().contains("ghost")); + } +} diff --git a/plugin/src/test/java/ru/abstractmenus/addon/AddonManagerIntegrationTest.java b/plugin/src/test/java/ru/abstractmenus/addon/AddonManagerIntegrationTest.java new file mode 100644 index 0000000..e021a5f --- /dev/null +++ b/plugin/src/test/java/ru/abstractmenus/addon/AddonManagerIntegrationTest.java @@ -0,0 +1,175 @@ +package ru.abstractmenus.addon; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import ru.abstractmenus.api.AbstractMenusApi; +import ru.abstractmenus.api.Action; +import ru.abstractmenus.api.Activator; +import ru.abstractmenus.api.Catalog; +import ru.abstractmenus.api.MenuExtension; +import ru.abstractmenus.api.Rule; +import ru.abstractmenus.api.ProviderRegistry; +import ru.abstractmenus.api.ProviderRegistryImpl; +import ru.abstractmenus.api.TypeRegistry; +import ru.abstractmenus.api.TypeRegistryImpl; +import ru.abstractmenus.api.inventory.ItemProperty; +import ru.abstractmenus.api.inventory.Menu; +import ru.abstractmenus.api.variables.VariableManager; +import ru.abstractmenus.hocon.api.ConfigNode; +import ru.abstractmenus.hocon.api.serialize.NodeSerializeException; +import ru.abstractmenus.hocon.api.serialize.NodeSerializer; +import ru.abstractmenus.hocon.api.serialize.NodeSerializers; + +import java.io.ByteArrayOutputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * End-to-end integration test for {@link AddonManager}: builds a real addon + * jar at test-time via {@link JarOutputStream}, feeds it through + * {@code loadAll()}, verifies the addon's action is registered, then verifies + * {@code unloadAll()} strips the registration. + * + *

Uses a pure-unit {@link StubApi} + the package-private test-only + * {@code AddonManager(Path, AbstractMenusApi)} constructor, since MockBukkit + * does not work with Paper 1.21.11 (see {@code CoreExtensionTest}). + */ +class AddonManagerIntegrationTest { + + @BeforeAll + static void initLogger() { + // AddonManager uses ru.abstractmenus.api.Logger, which delegates to a + // static java.util.logging.Logger set by the plugin at runtime. In the + // test environment we must inject one ourselves or any logging path + // inside loadAll will NPE. + ru.abstractmenus.api.Logger.set( + java.util.logging.Logger.getLogger("AddonManagerIntegrationTest")); + } + + /** Test fixture — a MenuExtension that registers one action. */ + public static class TestAddon implements MenuExtension { + @Override public void onEnable(AbstractMenusApi api) { + api.actions().register("testPing", PingAction.class, + new PingAction.Serializer(), this); + } + @Override public String name() { return "TestAddon"; } + @Override public String version() { return "1.0.0"; } + } + + /** Test fixture — a plain Action. */ + public static class PingAction implements Action { + @Override + public void activate(org.bukkit.entity.Player player, + ru.abstractmenus.api.inventory.Menu menu, + ru.abstractmenus.api.inventory.Item clickedItem) { + // no-op + } + + public static class Serializer implements NodeSerializer { + @Override + public PingAction deserialize(Class type, ConfigNode node) + throws NodeSerializeException { + return new PingAction(); + } + } + } + + @Test + void loadAll_readsJar_enablesAddon_registersAction(@TempDir Path serverRoot) throws Exception { + // Layout: /plugins/AbstractMenus/addons/ + Path dataFolder = Files.createDirectories( + serverRoot.resolve("plugins").resolve("AbstractMenus")); + Path addonsDir = Files.createDirectories(dataFolder.resolve("addons")); + + // Pack TestAddon + PingAction + its Serializer + addon.conf into a jar. + Path jar = addonsDir.resolve("test-addon.jar"); + String hocon = """ + name = "TestAddon" + version = "1.0.0" + main = "%s" + """.formatted(TestAddon.class.getName()); + + try (JarOutputStream out = new JarOutputStream(new FileOutputStream(jar.toFile()))) { + out.putNextEntry(new JarEntry("addon.conf")); + out.write(hocon.getBytes(StandardCharsets.UTF_8)); + out.closeEntry(); + packClass(out, TestAddon.class); + packClass(out, PingAction.class); + packClass(out, PingAction.Serializer.class); + } + + // Build a minimal AbstractMenusApi stub backed by real registries. + StubApi api = new StubApi(); + + // Use the test-only constructor. + AddonManager manager = new AddonManager(addonsDir, api); + manager.loadAll(); + + // Verify: exactly one addon is loaded, with ENABLED status. + assertEquals(1, manager.loaded().size()); + LoadedAddon loaded = manager.loaded().iterator().next(); + assertEquals("TestAddon", loaded.getConf().name()); + assertEquals(AddonStatus.ENABLED, loaded.getStatus(), + "addon must reach ENABLED status (error: " + + (loaded.getError() == null ? "none" : loaded.getError()) + ")"); + + // Verify: the action is registered. + assertNotNull(api.actions().get("testPing"), + "TestAddon must register action 'testPing'"); + + // Verify: unload cleans up. + manager.unloadAll(); + assertNull(api.actions().get("testPing"), + "unloadAll should strip the addon's registrations"); + assertEquals(0, manager.loaded().size()); + } + + // --- helpers --- + + private static void packClass(JarOutputStream out, Class cls) throws Exception { + String path = cls.getName().replace('.', '/') + ".class"; + try (InputStream in = cls.getClassLoader().getResourceAsStream(path); + ByteArrayOutputStream buf = new ByteArrayOutputStream()) { + if (in == null) throw new IllegalStateException("cannot find " + path); + in.transferTo(buf); + out.putNextEntry(new JarEntry(path)); + out.write(buf.toByteArray()); + out.closeEntry(); + } + } + + /** Minimal AbstractMenusApi — real registries, stub lifecycle. */ + private static class StubApi implements AbstractMenusApi { + private final NodeSerializers serializers = NodeSerializers.defaults(); + private final TypeRegistry actions = new TypeRegistryImpl<>(serializers); + private final TypeRegistry rules = new TypeRegistryImpl<>(serializers); + private final TypeRegistry activators = new TypeRegistryImpl<>(serializers); + private final TypeRegistry itemProperties = new TypeRegistryImpl<>(serializers); + private final TypeRegistry> catalogs = new TypeRegistryImpl<>(serializers); + private final ProviderRegistry providers = new ProviderRegistryImpl(); + + @Override public TypeRegistry actions() { return actions; } + @Override public TypeRegistry rules() { return rules; } + @Override public TypeRegistry activators() { return activators; } + @Override public TypeRegistry itemProperties() { return itemProperties; } + @Override public TypeRegistry> catalogs() { return catalogs; } + @Override public ProviderRegistry providers() { return providers; } + @Override public NodeSerializers serializers() { return serializers; } + @Override public VariableManager variables() { return null; } + @Override public org.bukkit.plugin.Plugin getPlugin() { return null; } + @Override public void loadMenus() {} + @Override public void openMenu(Activator activator, Object ctx, org.bukkit.entity.Player player, Menu menu) {} + @Override public void openMenu(org.bukkit.entity.Player player, Menu menu) {} + @Override public Optional

getOpenedMenu(org.bukkit.entity.Player player) { return Optional.empty(); } + @Override public String apiVersion() { return "test"; } + } +} diff --git a/plugin/src/test/java/ru/abstractmenus/api/ProviderRegistryImplTest.java b/plugin/src/test/java/ru/abstractmenus/api/ProviderRegistryImplTest.java new file mode 100644 index 0000000..a113940 --- /dev/null +++ b/plugin/src/test/java/ru/abstractmenus/api/ProviderRegistryImplTest.java @@ -0,0 +1,146 @@ +package ru.abstractmenus.api; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import ru.abstractmenus.api.handler.EconomyHandler; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; + +class ProviderRegistryImplTest { + + private ProviderRegistryImpl registry; + private MenuExtension ownerA; + private MenuExtension ownerB; + + @BeforeEach + void setUp() { + registry = new ProviderRegistryImpl(); + ownerA = new DummyExtension("A"); + ownerB = new DummyExtension("B"); + } + + @Test + void register_singleProvider_resolvesByIdAndAuto() { + EconomyHandler vault = mock(EconomyHandler.class); + registry.registerEconomy("vault", vault, 50, ownerA); + + assertSame(vault, registry.economy()); + assertSame(vault, registry.economy("vault")); + assertEquals(1, registry.allEconomy().size()); + assertTrue(registry.hasEconomy("vault")); + } + + @Test + void auto_returnsNullWhenEmpty() { + assertNull(registry.economy()); + assertFalse(registry.hasEconomy("anything")); + } + + @Test + void auto_highestPriorityWins() { + EconomyHandler vault = mock(EconomyHandler.class); + EconomyHandler pp = mock(EconomyHandler.class); + registry.registerEconomy("vault", vault, 50, ownerA); + registry.registerEconomy("playerpoints", pp, 100, ownerA); + + assertSame(pp, registry.economy()); + } + + @Test + void auto_tieBreaksToFirstRegistered() { + EconomyHandler a = mock(EconomyHandler.class); + EconomyHandler b = mock(EconomyHandler.class); + registry.registerEconomy("alpha", a, 50, ownerA); + registry.registerEconomy("beta", b, 50, ownerA); + + assertSame(a, registry.economy()); + } + + @Test + void lookupById_caseInsensitive() { + EconomyHandler vault = mock(EconomyHandler.class); + registry.registerEconomy("Vault", vault, 50, ownerA); + + assertSame(vault, registry.economy("VAULT")); + assertSame(vault, registry.economy("vault")); + assertTrue(registry.hasEconomy("vAuLt")); + } + + @Test + void unregisterAll_removesOnlyThatOwner() { + EconomyHandler ea = mock(EconomyHandler.class); + EconomyHandler eb = mock(EconomyHandler.class); + registry.registerEconomy("a", ea, 50, ownerA); + registry.registerEconomy("b", eb, 50, ownerB); + + registry.unregisterAll(ownerA); + + assertNull(registry.economy("a")); + assertSame(eb, registry.economy("b")); + assertEquals(1, registry.allEconomy().size()); + } + + @Test + void overwrite_replacesPrevious() { + EconomyHandler old = mock(EconomyHandler.class); + EconomyHandler fresh = mock(EconomyHandler.class); + registry.registerEconomy("vault", old, 50, ownerA); + registry.registerEconomy("vault", fresh, 50, ownerB); + + assertSame(fresh, registry.economy("vault")); + } + + @Test + void sectionsAreIndependent() { + EconomyHandler e = mock(EconomyHandler.class); + registry.registerEconomy("e", e, 50, ownerA); + + assertNull(registry.permissions()); + assertNull(registry.levels()); + assertNull(registry.placeholders()); + assertNull(registry.skins()); + } + + @Test + void configDefault_prefersConfiguredId() { + EconomyHandler vault = mock(EconomyHandler.class); + EconomyHandler pp = mock(EconomyHandler.class); + registry.registerEconomy("vault", vault, 50, ownerA); + registry.registerEconomy("playerpoints", pp, 100, ownerA); + + // Without config, auto prefers playerpoints (priority 100). + assertSame(pp, registry.economy()); + + // With config override to vault, vault wins despite lower priority. + registry.setConfigDefaults(kind -> "economy".equals(kind) ? "vault" : null); + assertSame(vault, registry.economy()); + } + + @Test + void configDefault_autoKeyword_fallsBackToAuto() { + EconomyHandler vault = mock(EconomyHandler.class); + registry.registerEconomy("vault", vault, 50, ownerA); + + registry.setConfigDefaults(kind -> "auto"); + assertSame(vault, registry.economy()); + } + + @Test + void configDefault_unknownId_fallsBackToAuto() { + EconomyHandler vault = mock(EconomyHandler.class); + registry.registerEconomy("vault", vault, 50, ownerA); + + registry.setConfigDefaults(kind -> "ghost"); // not registered + assertSame(vault, registry.economy()); // auto fallback + } + + // --- helper --- + + static class DummyExtension implements MenuExtension { + private final String name; + DummyExtension(String name) { this.name = name; } + @Override public void onEnable(AbstractMenusApi api) {} + @Override public String name() { return name; } + } +} diff --git a/plugin/src/test/java/ru/abstractmenus/api/TypeRegistryImplTest.java b/plugin/src/test/java/ru/abstractmenus/api/TypeRegistryImplTest.java new file mode 100644 index 0000000..a422010 --- /dev/null +++ b/plugin/src/test/java/ru/abstractmenus/api/TypeRegistryImplTest.java @@ -0,0 +1,115 @@ +package ru.abstractmenus.api; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import ru.abstractmenus.hocon.api.ConfigNode; +import ru.abstractmenus.hocon.api.serialize.NodeSerializer; +import ru.abstractmenus.hocon.api.serialize.NodeSerializers; +import ru.abstractmenus.hocon.api.serialize.NodeSerializeException; + +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; + +class TypeRegistryImplTest { + + interface Widget { } + static class RedWidget implements Widget { } + static class BlueWidget implements Widget { } + + private NodeSerializers serializers; + private TypeRegistryImpl registry; + private MenuExtension ownerA; + private MenuExtension ownerB; + + @BeforeEach + void setUp() { + serializers = NodeSerializers.defaults(); + registry = new TypeRegistryImpl<>(serializers); + ownerA = new DummyExtension("A"); + ownerB = new DummyExtension("B"); + } + + @Test + void register_exposesKey() { + NodeSerializer ser = dummySerializer(); + registry.register("red", RedWidget.class, ser, ownerA); + + assertEquals(RedWidget.class, registry.get("red")); + assertEquals("red", registry.name(RedWidget.class)); + assertEquals(Set.of("red"), registry.keys()); + } + + @Test + void register_isCaseInsensitive() { + NodeSerializer ser = dummySerializer(); + registry.register("Red", RedWidget.class, ser, ownerA); + + assertEquals(RedWidget.class, registry.get("RED")); + assertEquals(RedWidget.class, registry.get("red")); + assertEquals("red", registry.name(RedWidget.class)); + } + + @Test + void get_returnsNullForUnknown() { + assertNull(registry.get("nothing")); + } + + @Test + void unregisterAll_removesOnlyThatOwnersEntries() { + registry.register("red", RedWidget.class, dummySerializer(), ownerA); + registry.register("blue", BlueWidget.class, dummySerializer(), ownerB); + + registry.unregisterAll(ownerA); + + assertNull(registry.get("red")); + assertEquals(BlueWidget.class, registry.get("blue")); + assertEquals(Set.of("blue"), registry.keys()); + } + + @Test + void register_overwrite_replaces() { + registry.register("red", RedWidget.class, dummySerializer(), ownerA); + registry.register("red", BlueWidget.class, dummySerializer(), ownerB); + + assertEquals(BlueWidget.class, registry.get("red")); + assertNull(registry.name(RedWidget.class)); + } + + @Test + void unregisterAll_withNoRegistrations_isSafe() { + // Should not throw even if the owner has no entries. + registry.unregisterAll(ownerA); + assertEquals(Set.of(), registry.keys()); + } + + @Test + void keys_returnsLowercased() { + registry.register("RedWidget", RedWidget.class, dummySerializer(), ownerA); + assertEquals(Set.of("redwidget"), registry.keys()); + } + + // --- helpers --- + + /** + * NodeSerializer has one abstract method: + * T deserialize(Class, ConfigNode) throws NodeSerializeException + * Stubs return null (safe for tests that never actually deserialize). + */ + @SuppressWarnings("unchecked") + private NodeSerializer dummySerializer() { + return new NodeSerializer() { + @Override + public T deserialize(Class type, ConfigNode node) throws NodeSerializeException { + return null; + } + }; + } + + static class DummyExtension implements MenuExtension { + private final String name; + DummyExtension(String name) { this.name = name; } + @Override public void onEnable(AbstractMenusApi api) { } + @Override public String name() { return name; } + } +} diff --git a/src/test/java/ru/abstractmenus/bench/ConcurrencyBenchmark.java b/plugin/src/test/java/ru/abstractmenus/bench/ConcurrencyBenchmark.java similarity index 100% rename from src/test/java/ru/abstractmenus/bench/ConcurrencyBenchmark.java rename to plugin/src/test/java/ru/abstractmenus/bench/ConcurrencyBenchmark.java diff --git a/src/test/java/ru/abstractmenus/bench/ItemBuildBenchmark.java b/plugin/src/test/java/ru/abstractmenus/bench/ItemBuildBenchmark.java similarity index 100% rename from src/test/java/ru/abstractmenus/bench/ItemBuildBenchmark.java rename to plugin/src/test/java/ru/abstractmenus/bench/ItemBuildBenchmark.java diff --git a/src/test/java/ru/abstractmenus/bench/PlaceholderReplaceBenchmark.java b/plugin/src/test/java/ru/abstractmenus/bench/PlaceholderReplaceBenchmark.java similarity index 100% rename from src/test/java/ru/abstractmenus/bench/PlaceholderReplaceBenchmark.java rename to plugin/src/test/java/ru/abstractmenus/bench/PlaceholderReplaceBenchmark.java diff --git a/src/test/java/ru/abstractmenus/bench/RunBenchmarks.java b/plugin/src/test/java/ru/abstractmenus/bench/RunBenchmarks.java similarity index 100% rename from src/test/java/ru/abstractmenus/bench/RunBenchmarks.java rename to plugin/src/test/java/ru/abstractmenus/bench/RunBenchmarks.java diff --git a/src/test/java/ru/abstractmenus/bench/SlotContainsBenchmark.java b/plugin/src/test/java/ru/abstractmenus/bench/SlotContainsBenchmark.java similarity index 100% rename from src/test/java/ru/abstractmenus/bench/SlotContainsBenchmark.java rename to plugin/src/test/java/ru/abstractmenus/bench/SlotContainsBenchmark.java diff --git a/src/test/java/ru/abstractmenus/bench/StringSplitBenchmark.java b/plugin/src/test/java/ru/abstractmenus/bench/StringSplitBenchmark.java similarity index 100% rename from src/test/java/ru/abstractmenus/bench/StringSplitBenchmark.java rename to plugin/src/test/java/ru/abstractmenus/bench/StringSplitBenchmark.java diff --git a/plugin/src/test/java/ru/abstractmenus/core/CoreExtensionTest.java b/plugin/src/test/java/ru/abstractmenus/core/CoreExtensionTest.java new file mode 100644 index 0000000..d597de3 --- /dev/null +++ b/plugin/src/test/java/ru/abstractmenus/core/CoreExtensionTest.java @@ -0,0 +1,172 @@ +package ru.abstractmenus.core; + +import org.bukkit.Bukkit; +import org.bukkit.Server; +import org.bukkit.plugin.PluginManager; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import ru.abstractmenus.api.AbstractMenusApi; +import ru.abstractmenus.api.Action; +import ru.abstractmenus.api.Activator; +import ru.abstractmenus.api.Catalog; +import ru.abstractmenus.api.MenuExtension; +import ru.abstractmenus.api.Rule; +import ru.abstractmenus.api.ProviderRegistry; +import ru.abstractmenus.api.ProviderRegistryImpl; +import ru.abstractmenus.api.TypeRegistry; +import ru.abstractmenus.api.TypeRegistryImpl; +import ru.abstractmenus.api.inventory.ItemProperty; +import ru.abstractmenus.api.inventory.Menu; +import ru.abstractmenus.api.variables.VariableManager; +import ru.abstractmenus.hocon.api.serialize.NodeSerializers; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +/** + * Verifies that {@link CoreExtension#onEnable(AbstractMenusApi)} registers the + * expected number of core types into the five registries. + * + *

This test does NOT boot the full plugin — it constructs a lightweight + * {@link StubApi} backed by real {@link TypeRegistryImpl} instances and invokes + * {@code CoreExtension.onEnable()} directly, bypassing the MockBukkit + * incompatibility with Paper 1.21.11 that prevents the full plugin from loading + * in tests (see {@link ru.abstractmenus.integration.TestPluginLifecycle}). + * + *

Activator count note: {@link CoreActivatorsBundle} conditionally + * registers "clickNPC" (Citizens) and "regionJoin"/"regionLeave" (WorldGuard) + * via {@code AbstractMenus.checkDependency()}. In this test environment those + * soft-deps are absent, so the activator count is 14 (not 17 as in full + * production). The 17 figure in the Phase B.1 spec counts all three conditional + * registrations as present. + */ +class CoreExtensionTest { + + private StubApi api; + private MockedStatic mockedBukkit; + + @BeforeEach + void setUp() { + // CoreActivatorsBundle calls AbstractMenus.checkDependency() which calls + // Bukkit.getServer().getPluginManager().isPluginEnabled(...). + // Mock the static Bukkit.getServer() so the call doesn't NPE. + // Return false for every plugin so only unconditional activators register. + PluginManager pluginManager = mock(PluginManager.class); + when(pluginManager.isPluginEnabled(anyString())).thenReturn(false); + + Server server = mock(Server.class); + when(server.getPluginManager()).thenReturn(pluginManager); + + mockedBukkit = mockStatic(Bukkit.class); + mockedBukkit.when(Bukkit::getServer).thenReturn(server); + + api = new StubApi(); + } + + @AfterEach + void tearDown() { + mockedBukkit.close(); + } + + @Test + void coreExtension_registersExpectedActions() { + new CoreExtension().onEnable(api); + assertEquals(65, api.actions().keys().size(), + "expected 65 core action types (matches Task 8 migration count)"); + } + + @Test + void coreExtension_registersExpectedRules() { + new CoreExtension().onEnable(api); + assertEquals(28, api.rules().keys().size(), + "expected 28 core rule types (matches Task 9 migration count)"); + } + + @Test + void coreExtension_registersExpectedItemProperties() { + new CoreExtension().onEnable(api); + assertEquals(32, api.itemProperties().keys().size(), + "expected 32 core item-property types (matches Task 10 migration count)"); + } + + @Test + void coreExtension_registersExpectedActivators_withoutOptionalDeps() { + // 14 unconditional activators; Citizens (+1) and WorldGuard (+2) are absent + // in this test environment — see class-level Javadoc for details. + new CoreExtension().onEnable(api); + assertEquals(14, api.activators().keys().size(), + "expected 14 unconditional core activator types " + + "(17 in production with Citizens + WorldGuard present)"); + } + + @Test + void coreExtension_registersExpectedCatalogs() { + new CoreExtension().onEnable(api); + assertEquals(6, api.catalogs().keys().size(), + "expected 6 core catalog types (matches Task 12 migration count)"); + } + + @Test + void coreExtension_canonicalActions_resolve() { + new CoreExtension().onEnable(api); + assertNotNull(api.actions().get("openMenu"), "openMenu must be registered"); + assertNotNull(api.actions().get("closeMenu"), "closeMenu must be registered"); + } + + @Test + void coreExtension_canonicalRules_resolve() { + new CoreExtension().onEnable(api); + assertNotNull(api.rules().get("permission"), "permission rule must be registered"); + } + + @Test + void coreExtension_canonicalItemProperty_resolve() { + new CoreExtension().onEnable(api); + assertNotNull(api.itemProperties().get("material"), "material item-property must be registered"); + } + + // ------------------------------------------------------------------------- + // Minimal AbstractMenusApi stub backed by real TypeRegistryImpl instances. + // Only the registry accessor methods are implemented; all others throw + // UnsupportedOperationException to catch accidental calls. + // ------------------------------------------------------------------------- + + private static final class StubApi implements AbstractMenusApi { + + private final NodeSerializers serializers = NodeSerializers.defaults(); + + private final TypeRegistry actions = new TypeRegistryImpl<>(serializers); + private final TypeRegistry rules = new TypeRegistryImpl<>(serializers); + private final TypeRegistry activators = new TypeRegistryImpl<>(serializers); + private final TypeRegistry itemProperties = new TypeRegistryImpl<>(serializers); + private final TypeRegistry> catalogs = new TypeRegistryImpl<>(serializers); + private final ProviderRegistry providers = new ProviderRegistryImpl(); + + @Override public TypeRegistry actions() { return actions; } + @Override public TypeRegistry rules() { return rules; } + @Override public TypeRegistry activators() { return activators; } + @Override public TypeRegistry itemProperties() { return itemProperties; } + @Override public TypeRegistry> catalogs() { return catalogs; } + @Override public ProviderRegistry providers() { return providers; } + @Override public NodeSerializers serializers() { return serializers; } + + @Override public VariableManager variables() { throw new UnsupportedOperationException(); } + @Override public org.bukkit.plugin.Plugin getPlugin() { throw new UnsupportedOperationException(); } + @Override public void loadMenus() { throw new UnsupportedOperationException(); } + @Override public void openMenu(Activator a, Object ctx, org.bukkit.entity.Player p, Menu m) { throw new UnsupportedOperationException(); } + @Override public void openMenu(org.bukkit.entity.Player p, Menu m) { throw new UnsupportedOperationException(); } + @Override public Optional

getOpenedMenu(org.bukkit.entity.Player p) { throw new UnsupportedOperationException(); } + @Override public String apiVersion() { return "test"; } + } + + /** Minimal extension for test purposes (not used directly here). */ + private static final class DummyExtension implements MenuExtension { + @Override public String name() { return "TestExtension"; } + @Override public void onEnable(AbstractMenusApi api) {} + } +} diff --git a/plugin/src/test/java/ru/abstractmenus/data/MoneyProviderSelectionTest.java b/plugin/src/test/java/ru/abstractmenus/data/MoneyProviderSelectionTest.java new file mode 100644 index 0000000..4339271 --- /dev/null +++ b/plugin/src/test/java/ru/abstractmenus/data/MoneyProviderSelectionTest.java @@ -0,0 +1,216 @@ +package ru.abstractmenus.data; + +import org.bukkit.entity.Player; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import ru.abstractmenus.api.handler.EconomyHandler; +import ru.abstractmenus.data.actions.ActionMoneyGive; +import ru.abstractmenus.data.actions.ActionMoneyTake; +import ru.abstractmenus.data.rules.RuleMoney; +import ru.abstractmenus.datatype.TypeDouble; +import ru.abstractmenus.hocon.api.ConfigNode; +import ru.abstractmenus.hocon.api.ConfigurationLoader; +import ru.abstractmenus.hocon.api.serialize.NodeSerializeException; +import ru.abstractmenus.hocon.api.source.ConfigSources; +import ru.abstractmenus.testsupport.ApiTestSupport; + +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +/** + * End-to-end tests for provider: selection in ActionMoneyTake / ActionMoneyGive / RuleMoney. + * + *

Covers: + *

    + *
  1. Explicit {@code provider: "vault"} routes to the named handler.
  2. + *
  3. Omitted provider auto-resolves to the highest-priority handler.
  4. + *
  5. Scalar (legacy) form {@code takeMoney: 100.0} still deserializes.
  6. + *
  7. Unknown provider id throws {@link NodeSerializeException} at deserialize time.
  8. + *
+ */ +class MoneyProviderSelectionTest { + + /** vault: priority 50, pp: priority 100 — pp wins auto-resolve. */ + private static final int VAULT_PRIORITY = 50; + private static final int PP_PRIORITY = 100; + + private ApiTestSupport support; + private EconomyHandler vault; + private EconomyHandler pp; + + @BeforeEach + void setUp() { + support = ApiTestSupport.install(); + + vault = mock(EconomyHandler.class); + pp = mock(EconomyHandler.class); + + // Register TypeDouble.Serializer so the HOCON deserialization pipeline + // can resolve 'amount' nodes. In production this is done in Serializers.init(). + support.api().serializers().register(TypeDouble.class, new TypeDouble.Serializer()); + + support.providers().registerEconomy("vault", vault, VAULT_PRIORITY, support.owner()); + support.providers().registerEconomy("playerpoints", pp, PP_PRIORITY, support.owner()); + } + + @AfterEach + void tearDown() { + support.close(); + } + + // ------------------------------------------------------------------------- + // ActionMoneyTake + // ------------------------------------------------------------------------- + + @Test + void take_explicitProvider_usesNamedHandler() throws Exception { + String hocon = "amount = 100\nprovider = \"vault\""; + ActionMoneyTake action = new ActionMoneyTake.Serializer() + .deserialize(ActionMoneyTake.class, parseMap(hocon)); + + Player player = mock(Player.class); + action.activate(player, null, null); + + verify(vault).takeBalance(eq(player), eq(100.0)); + verify(pp, never()).takeBalance(any(), anyDouble()); + } + + @Test + void take_omittedProvider_usesHighestPriority() throws Exception { + // map form without provider field → auto-resolve → pp (priority 100 > 50) + String hocon = "amount = 50"; + ActionMoneyTake action = new ActionMoneyTake.Serializer() + .deserialize(ActionMoneyTake.class, parseMap(hocon)); + + Player player = mock(Player.class); + action.activate(player, null, null); + + verify(pp).takeBalance(eq(player), eq(50.0)); + verify(vault, never()).takeBalance(any(), anyDouble()); + } + + @Test + void take_scalarForm_backwardCompat() throws Exception { + // Legacy scalar: takeMoney: 100.0 — the node is a bare number, not a map. + ActionMoneyTake action = new ActionMoneyTake.Serializer() + .deserialize(ActionMoneyTake.class, parseScalar("100.0")); + + Player player = mock(Player.class); + action.activate(player, null, null); + + // Scalar form → no explicit provider → auto-resolve → pp + verify(pp).takeBalance(eq(player), eq(100.0)); + } + + @Test + void take_unknownProvider_throwsAtDeserialize() { + String hocon = "amount = 100\nprovider = \"ghost\""; + var serializer = new ActionMoneyTake.Serializer(); + + NodeSerializeException ex = assertThrows(NodeSerializeException.class, + () -> serializer.deserialize(ActionMoneyTake.class, parseMap(hocon))); + assertTrue(ex.getMessage().toLowerCase().contains("ghost"), + "error message should mention the unknown provider id; got: " + ex.getMessage()); + } + + // ------------------------------------------------------------------------- + // ActionMoneyGive + // ------------------------------------------------------------------------- + + @Test + void give_explicitProvider_usesNamedHandler() throws Exception { + String hocon = "amount = 200\nprovider = \"playerpoints\""; + ActionMoneyGive action = new ActionMoneyGive.Serializer() + .deserialize(ActionMoneyGive.class, parseMap(hocon)); + + Player player = mock(Player.class); + action.activate(player, null, null); + + verify(pp).giveBalance(eq(player), eq(200.0)); + verify(vault, never()).giveBalance(any(), anyDouble()); + } + + @Test + void give_unknownProvider_throwsAtDeserialize() { + String hocon = "amount = 200\nprovider = \"nowhere\""; + var serializer = new ActionMoneyGive.Serializer(); + + NodeSerializeException ex = assertThrows(NodeSerializeException.class, + () -> serializer.deserialize(ActionMoneyGive.class, parseMap(hocon))); + assertTrue(ex.getMessage().toLowerCase().contains("nowhere"), + "error message should mention the unknown provider id; got: " + ex.getMessage()); + } + + // ------------------------------------------------------------------------- + // RuleMoney + // ------------------------------------------------------------------------- + + @Test + void rule_explicitProvider_checksNamedHandler() throws Exception { + when(vault.hasBalance(any(), anyDouble())).thenReturn(true); + when(pp.hasBalance(any(), anyDouble())).thenReturn(false); + + String hocon = "amount = 500\nprovider = \"vault\""; + RuleMoney rule = new RuleMoney.Serializer() + .deserialize(RuleMoney.class, parseMap(hocon)); + + Player player = mock(Player.class); + boolean result = rule.check(player, null, null); + + assertTrue(result, "vault says true — rule should pass"); + verify(vault).hasBalance(eq(player), eq(500.0)); + verify(pp, never()).hasBalance(any(), anyDouble()); + } + + @Test + void rule_unknownProvider_throwsAtDeserialize() { + String hocon = "amount = 100\nprovider = \"missing\""; + var serializer = new RuleMoney.Serializer(); + + NodeSerializeException ex = assertThrows(NodeSerializeException.class, + () -> serializer.deserialize(RuleMoney.class, parseMap(hocon))); + assertTrue(ex.getMessage().toLowerCase().contains("missing"), + "error message should mention the unknown provider id; got: " + ex.getMessage()); + } + + // ------------------------------------------------------------------------- + // Parse helpers — use the test-support serializers so TypeDouble is known. + // ------------------------------------------------------------------------- + + /** + * Parse a HOCON snippet that represents a map (key = value pairs) into a + * ConfigNode using the same {@link ru.abstractmenus.hocon.api.serialize.NodeSerializers} + * instance that has {@code TypeDouble} registered. + */ + private ConfigNode parseMap(String hocon) throws Exception { + String wrapped = "val {\n" + hocon + "\n}"; + byte[] bytes = wrapped.getBytes(StandardCharsets.UTF_8); + ConfigNode root = ConfigurationLoader.builder() + .source(ConfigSources.inputStream("test", new ByteArrayInputStream(bytes))) + .serializers(support.api().serializers()) + .build() + .load(); + return root.node("val"); + } + + /** + * Parse a bare scalar value (e.g. {@code "100.0"}) into a ConfigNode that + * represents that scalar — mirrors how the legacy takeMoney: 100 form + * arrives at the Serializer (the node IS the value, not a map). + */ + private ConfigNode parseScalar(String scalar) throws Exception { + String wrapped = "val = " + scalar; + byte[] bytes = wrapped.getBytes(StandardCharsets.UTF_8); + ConfigNode root = ConfigurationLoader.builder() + .source(ConfigSources.inputStream("test", new ByteArrayInputStream(bytes))) + .serializers(support.api().serializers()) + .build() + .load(); + return root.node("val"); + } +} diff --git a/src/test/java/ru/abstractmenus/data/actions/TestActionCommandBehavior.java b/plugin/src/test/java/ru/abstractmenus/data/actions/TestActionCommandBehavior.java similarity index 82% rename from src/test/java/ru/abstractmenus/data/actions/TestActionCommandBehavior.java rename to plugin/src/test/java/ru/abstractmenus/data/actions/TestActionCommandBehavior.java index bba58e7..ff63a97 100644 --- a/src/test/java/ru/abstractmenus/data/actions/TestActionCommandBehavior.java +++ b/plugin/src/test/java/ru/abstractmenus/data/actions/TestActionCommandBehavior.java @@ -5,8 +5,8 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; -import ru.abstractmenus.api.Handlers; import ru.abstractmenus.api.handler.PlaceholderHandler; +import ru.abstractmenus.testsupport.ApiTestSupport; import ru.abstractmenus.hocon.api.ConfigNode; import ru.abstractmenus.hocon.api.ConfigurationLoader; import ru.abstractmenus.hocon.api.source.ConfigSources; @@ -26,12 +26,12 @@ */ class TestActionCommandBehavior { - private static PlaceholderHandler previousHandler; + private static ApiTestSupport apiSupport; @BeforeAll static void installIdentityHandler() { - previousHandler = Handlers.getPlaceholderHandler(); - Handlers.setPlaceholderHandler(new PlaceholderHandler() { + apiSupport = ApiTestSupport.install(); + apiSupport.installPlaceholderHandler(new PlaceholderHandler() { @Override public String replacePlaceholder(Player p, String s) { return s; } @Override public String replace(Player p, String s) { return s; } @Override public List replace(Player p, List l) { return l; } @@ -41,7 +41,7 @@ static void installIdentityHandler() { @AfterAll static void restore() { - Handlers.setPlaceholderHandler(previousHandler); + apiSupport.close(); } @Test @@ -84,13 +84,19 @@ void placeholderReplacementRunsExactlyOnce() throws Exception { // The old impl called replace(...) twice ("replace(replace(...))"), the // new one replaces once. Install a counting handler to enforce. int[] callCount = {0}; - PlaceholderHandler saved = Handlers.getPlaceholderHandler(); - Handlers.setPlaceholderHandler(new PlaceholderHandler() { + // Register a higher-priority counting handler that preempts the identity + // one installed in @BeforeAll; unregister it in the finally block so the + // identity handler remains active for the other tests. + ru.abstractmenus.api.MenuExtension scratchOwner = new ru.abstractmenus.api.MenuExtension() { + @Override public String name() { return "countingTestOwner"; } + @Override public void onEnable(ru.abstractmenus.api.AbstractMenusApi api) {} + }; + apiSupport.providers().registerPlaceholders("counting", new PlaceholderHandler() { @Override public String replacePlaceholder(Player p, String s) { return s; } @Override public String replace(Player p, String s) { callCount[0]++; return s; } @Override public List replace(Player p, List l) { return l; } @Override public void registerAll() {} - }); + }, 200, scratchOwner); try { ActionCommand action = buildAction("{ player = \"give %player_name% gold\" }"); Player player = mock(Player.class); @@ -100,7 +106,7 @@ void placeholderReplacementRunsExactlyOnce() throws Exception { org.junit.jupiter.api.Assertions.assertEquals(1, callCount[0], "PlaceholderHandler.replace must be called exactly once per command"); } finally { - Handlers.setPlaceholderHandler(saved); + apiSupport.providers().unregisterAll(scratchOwner); } } diff --git a/src/test/java/ru/abstractmenus/data/properties/TestPropNameLorePrecompute.java b/plugin/src/test/java/ru/abstractmenus/data/properties/TestPropNameLorePrecompute.java similarity index 96% rename from src/test/java/ru/abstractmenus/data/properties/TestPropNameLorePrecompute.java rename to plugin/src/test/java/ru/abstractmenus/data/properties/TestPropNameLorePrecompute.java index b63656a..6c9e58a 100644 --- a/src/test/java/ru/abstractmenus/data/properties/TestPropNameLorePrecompute.java +++ b/plugin/src/test/java/ru/abstractmenus/data/properties/TestPropNameLorePrecompute.java @@ -7,12 +7,12 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; -import ru.abstractmenus.api.Handlers; import ru.abstractmenus.api.handler.PlaceholderHandler; import ru.abstractmenus.api.text.Colors; import ru.abstractmenus.hocon.api.ConfigNode; import ru.abstractmenus.hocon.api.ConfigurationLoader; import ru.abstractmenus.hocon.api.source.ConfigSources; +import ru.abstractmenus.testsupport.ApiTestSupport; import ru.abstractmenus.util.MiniMessageUtil; import java.io.ByteArrayInputStream; @@ -37,15 +37,15 @@ */ class TestPropNameLorePrecompute { - private static PlaceholderHandler previousHandler; + private static ApiTestSupport apiSupport; private static int replaceCallCount; @BeforeAll static void setUp() { MiniMessageUtil.init(false); Colors.init(false); // pass-through; avoids LegacyComponentSerializer init. - previousHandler = Handlers.getPlaceholderHandler(); - Handlers.setPlaceholderHandler(new PlaceholderHandler() { + apiSupport = ApiTestSupport.install(); + apiSupport.installPlaceholderHandler(new PlaceholderHandler() { @Override public String replacePlaceholder(Player p, String s) { return s; } @Override public String replace(Player p, String s) { replaceCallCount++; return s; } @Override public List replace(Player p, List l) { replaceCallCount++; return l; } @@ -55,7 +55,7 @@ static void setUp() { @AfterAll static void tearDown() { - Handlers.setPlaceholderHandler(previousHandler); + apiSupport.close(); // Intentionally leave MiniMessageUtil in inactive mode: re-init(true) // would trigger LegacyComponentSerializer.builder(), which collides // on the test classpath (two Adventure Provider impls). No other test diff --git a/src/test/java/ru/abstractmenus/datatype/TestCompoundDataTypes.java b/plugin/src/test/java/ru/abstractmenus/datatype/TestCompoundDataTypes.java similarity index 97% rename from src/test/java/ru/abstractmenus/datatype/TestCompoundDataTypes.java rename to plugin/src/test/java/ru/abstractmenus/datatype/TestCompoundDataTypes.java index aa727a7..8c9324f 100644 --- a/src/test/java/ru/abstractmenus/datatype/TestCompoundDataTypes.java +++ b/plugin/src/test/java/ru/abstractmenus/datatype/TestCompoundDataTypes.java @@ -6,8 +6,8 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import ru.abstractmenus.api.Handlers; import ru.abstractmenus.api.handler.PlaceholderHandler; +import ru.abstractmenus.testsupport.ApiTestSupport; import ru.abstractmenus.api.inventory.Slot; import ru.abstractmenus.api.inventory.slot.SlotIndex; import ru.abstractmenus.api.inventory.slot.SlotPos; @@ -33,13 +33,13 @@ */ class TestCompoundDataTypes { - private static PlaceholderHandler previousHandler; + private static ApiTestSupport apiSupport; @BeforeAll static void installIdentityPlaceholderHandler() { - previousHandler = Handlers.getPlaceholderHandler(); + apiSupport = ApiTestSupport.install(); // Identity handler — tests in this file don't exercise actual replacement. - Handlers.setPlaceholderHandler(new PlaceholderHandler() { + apiSupport.installPlaceholderHandler(new PlaceholderHandler() { @Override public String replacePlaceholder(org.bukkit.entity.Player p, String s) { return s; } @Override public String replace(org.bukkit.entity.Player p, String s) { return s; } @Override public List replace(org.bukkit.entity.Player p, List l) { return l; } @@ -49,7 +49,7 @@ static void installIdentityPlaceholderHandler() { @AfterAll static void restorePlaceholderHandler() { - Handlers.setPlaceholderHandler(previousHandler); + apiSupport.close(); } private static ConfigNode loadValueNode(String hocon) throws Exception { diff --git a/src/test/java/ru/abstractmenus/datatype/TestHasPlaceholder.java b/plugin/src/test/java/ru/abstractmenus/datatype/TestHasPlaceholder.java similarity index 100% rename from src/test/java/ru/abstractmenus/datatype/TestHasPlaceholder.java rename to plugin/src/test/java/ru/abstractmenus/datatype/TestHasPlaceholder.java diff --git a/src/test/java/ru/abstractmenus/datatype/TestPrimitiveDataTypes.java b/plugin/src/test/java/ru/abstractmenus/datatype/TestPrimitiveDataTypes.java similarity index 97% rename from src/test/java/ru/abstractmenus/datatype/TestPrimitiveDataTypes.java rename to plugin/src/test/java/ru/abstractmenus/datatype/TestPrimitiveDataTypes.java index f10fd54..2c4ff3c 100644 --- a/src/test/java/ru/abstractmenus/datatype/TestPrimitiveDataTypes.java +++ b/plugin/src/test/java/ru/abstractmenus/datatype/TestPrimitiveDataTypes.java @@ -4,8 +4,8 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import ru.abstractmenus.api.Handlers; import ru.abstractmenus.api.handler.PlaceholderHandler; +import ru.abstractmenus.testsupport.ApiTestSupport; import ru.abstractmenus.hocon.api.ConfigNode; import ru.abstractmenus.hocon.api.ConfigurationLoader; import ru.abstractmenus.hocon.api.serialize.NodeSerializeException; @@ -26,14 +26,14 @@ */ class TestPrimitiveDataTypes { - private static PlaceholderHandler previousHandler; + private static ApiTestSupport apiSupport; private static final Map placeholderMap = new HashMap<>(); @BeforeAll static void setUpPlaceholders() { - previousHandler = Handlers.getPlaceholderHandler(); + apiSupport = ApiTestSupport.install(); // Minimal PlaceholderHandler that echoes lookups from the map; unresolved → returned as-is. - Handlers.setPlaceholderHandler(new PlaceholderHandler() { + apiSupport.installPlaceholderHandler(new PlaceholderHandler() { @Override public String replacePlaceholder(org.bukkit.entity.Player p, String s) { return replace(p, s); @@ -61,7 +61,7 @@ public void registerAll() {} @AfterAll static void tearDown() { - Handlers.setPlaceholderHandler(previousHandler); + apiSupport.close(); placeholderMap.clear(); } diff --git a/src/test/java/ru/abstractmenus/datatype/TestTypeMaterial.java b/plugin/src/test/java/ru/abstractmenus/datatype/TestTypeMaterial.java similarity index 100% rename from src/test/java/ru/abstractmenus/datatype/TestTypeMaterial.java rename to plugin/src/test/java/ru/abstractmenus/datatype/TestTypeMaterial.java diff --git a/src/test/java/ru/abstractmenus/datatype/TestTypeMaterialSerializer.java b/plugin/src/test/java/ru/abstractmenus/datatype/TestTypeMaterialSerializer.java similarity index 100% rename from src/test/java/ru/abstractmenus/datatype/TestTypeMaterialSerializer.java rename to plugin/src/test/java/ru/abstractmenus/datatype/TestTypeMaterialSerializer.java diff --git a/src/test/java/ru/abstractmenus/integration/TestPluginLifecycle.java b/plugin/src/test/java/ru/abstractmenus/integration/TestPluginLifecycle.java similarity index 100% rename from src/test/java/ru/abstractmenus/integration/TestPluginLifecycle.java rename to plugin/src/test/java/ru/abstractmenus/integration/TestPluginLifecycle.java diff --git a/src/test/java/ru/abstractmenus/menu/item/TestSimpleItemCopyOnWrite.java b/plugin/src/test/java/ru/abstractmenus/menu/item/TestSimpleItemCopyOnWrite.java similarity index 100% rename from src/test/java/ru/abstractmenus/menu/item/TestSimpleItemCopyOnWrite.java rename to plugin/src/test/java/ru/abstractmenus/menu/item/TestSimpleItemCopyOnWrite.java diff --git a/src/test/java/ru/abstractmenus/serializers/TestColorSerializer.java b/plugin/src/test/java/ru/abstractmenus/serializers/TestColorSerializer.java similarity index 100% rename from src/test/java/ru/abstractmenus/serializers/TestColorSerializer.java rename to plugin/src/test/java/ru/abstractmenus/serializers/TestColorSerializer.java diff --git a/src/test/java/ru/abstractmenus/serializers/TestEntityTypeSerializer.java b/plugin/src/test/java/ru/abstractmenus/serializers/TestEntityTypeSerializer.java similarity index 100% rename from src/test/java/ru/abstractmenus/serializers/TestEntityTypeSerializer.java rename to plugin/src/test/java/ru/abstractmenus/serializers/TestEntityTypeSerializer.java diff --git a/src/test/java/ru/abstractmenus/serializers/TestJsonSerializer.java b/plugin/src/test/java/ru/abstractmenus/serializers/TestJsonSerializer.java similarity index 100% rename from src/test/java/ru/abstractmenus/serializers/TestJsonSerializer.java rename to plugin/src/test/java/ru/abstractmenus/serializers/TestJsonSerializer.java diff --git a/plugin/src/test/java/ru/abstractmenus/testsupport/ApiTestSupport.java b/plugin/src/test/java/ru/abstractmenus/testsupport/ApiTestSupport.java new file mode 100644 index 0000000..1168305 --- /dev/null +++ b/plugin/src/test/java/ru/abstractmenus/testsupport/ApiTestSupport.java @@ -0,0 +1,121 @@ +package ru.abstractmenus.testsupport; + +import org.bukkit.Bukkit; +import org.bukkit.plugin.ServicesManager; +import org.mockito.MockedStatic; +import ru.abstractmenus.api.AbstractMenusApi; +import ru.abstractmenus.api.Action; +import ru.abstractmenus.api.Activator; +import ru.abstractmenus.api.Catalog; +import ru.abstractmenus.api.MenuExtension; +import ru.abstractmenus.api.ProviderRegistry; +import ru.abstractmenus.api.ProviderRegistryImpl; +import ru.abstractmenus.api.Rule; +import ru.abstractmenus.api.TypeRegistry; +import ru.abstractmenus.api.TypeRegistryImpl; +import ru.abstractmenus.api.handler.PlaceholderHandler; +import ru.abstractmenus.api.inventory.ItemProperty; +import ru.abstractmenus.api.inventory.Menu; +import ru.abstractmenus.api.variables.VariableManager; +import ru.abstractmenus.hocon.api.serialize.NodeSerializers; + +import java.util.Optional; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +/** + * Helper for unit tests that exercise production code calling + * {@code AbstractMenusApi.get().providers().*()}. + * + *

Stubs the static {@link Bukkit#getServicesManager()} with a real + * {@link ServicesManager} mock that returns a lightweight {@link StubApi} + * backed by a real {@link ProviderRegistryImpl}. Tests can register the + * stub handlers they need directly on the registry. + * + *

Typical usage: + *

{@code
+ * private static ApiTestSupport support;
+ *
+ * @BeforeAll
+ * static void setUp() {
+ *     support = ApiTestSupport.install();
+ *     support.providers().registerPlaceholders("test", myHandler, 100, support.owner());
+ * }
+ *
+ * @AfterAll
+ * static void tearDown() { support.close(); }
+ * }
+ */ +public final class ApiTestSupport implements AutoCloseable { + + private final MockedStatic bukkitMock; + private final StubApi api; + private final TestOwner owner; + + private ApiTestSupport(MockedStatic bukkitMock, StubApi api, TestOwner owner) { + this.bukkitMock = bukkitMock; + this.api = api; + this.owner = owner; + } + + public static ApiTestSupport install() { + StubApi api = new StubApi(); + + ServicesManager servicesManager = mock(ServicesManager.class); + when(servicesManager.load(AbstractMenusApi.class)).thenReturn(api); + + MockedStatic bukkitMock = mockStatic(Bukkit.class); + bukkitMock.when(Bukkit::getServicesManager).thenReturn(servicesManager); + + return new ApiTestSupport(bukkitMock, api, new TestOwner()); + } + + public ProviderRegistry providers() { return api.providers(); } + public MenuExtension owner() { return owner; } + public AbstractMenusApi api() { return api; } + + /** Shortcut: register {@code handler} as the test placeholder provider. */ + public void installPlaceholderHandler(PlaceholderHandler handler) { + api.providers().registerPlaceholders("test", handler, 100, owner); + } + + @Override + public void close() { + bukkitMock.close(); + } + + // ------------------------------------------------------------------- + + private static final class TestOwner implements MenuExtension { + @Override public String name() { return "ApiTestSupport"; } + @Override public void onEnable(AbstractMenusApi api) {} + } + + private static final class StubApi implements AbstractMenusApi { + private final NodeSerializers serializers = NodeSerializers.defaults(); + private final TypeRegistry actions = new TypeRegistryImpl<>(serializers); + private final TypeRegistry rules = new TypeRegistryImpl<>(serializers); + private final TypeRegistry activators = new TypeRegistryImpl<>(serializers); + private final TypeRegistry itemProperties = new TypeRegistryImpl<>(serializers); + private final TypeRegistry> catalogs = new TypeRegistryImpl<>(serializers); + private final ProviderRegistry providers = new ProviderRegistryImpl(); + + @Override public TypeRegistry actions() { return actions; } + @Override public TypeRegistry rules() { return rules; } + @Override public TypeRegistry activators() { return activators; } + @Override public TypeRegistry itemProperties() { return itemProperties; } + @Override public TypeRegistry> catalogs() { return catalogs; } + @Override public ProviderRegistry providers() { return providers; } + @Override public NodeSerializers serializers() { return serializers; } + + @Override public VariableManager variables() { throw new UnsupportedOperationException(); } + @Override public org.bukkit.plugin.Plugin getPlugin() { throw new UnsupportedOperationException(); } + @Override public void loadMenus() { throw new UnsupportedOperationException(); } + @Override public void openMenu(Activator a, Object ctx, org.bukkit.entity.Player p, Menu m) { throw new UnsupportedOperationException(); } + @Override public void openMenu(org.bukkit.entity.Player p, Menu m) { throw new UnsupportedOperationException(); } + @Override public Optional getOpenedMenu(org.bukkit.entity.Player p) { throw new UnsupportedOperationException(); } + @Override public String apiVersion() { return "test"; } + } +} diff --git a/src/test/java/ru/abstractmenus/util/TestFileUtils.java b/plugin/src/test/java/ru/abstractmenus/util/TestFileUtils.java similarity index 100% rename from src/test/java/ru/abstractmenus/util/TestFileUtils.java rename to plugin/src/test/java/ru/abstractmenus/util/TestFileUtils.java diff --git a/src/test/java/ru/abstractmenus/util/TestLegacyColorTagReplacer.java b/plugin/src/test/java/ru/abstractmenus/util/TestLegacyColorTagReplacer.java similarity index 100% rename from src/test/java/ru/abstractmenus/util/TestLegacyColorTagReplacer.java rename to plugin/src/test/java/ru/abstractmenus/util/TestLegacyColorTagReplacer.java diff --git a/src/test/java/ru/abstractmenus/util/TestNumberUtil.java b/plugin/src/test/java/ru/abstractmenus/util/TestNumberUtil.java similarity index 100% rename from src/test/java/ru/abstractmenus/util/TestNumberUtil.java rename to plugin/src/test/java/ru/abstractmenus/util/TestNumberUtil.java diff --git a/src/test/java/ru/abstractmenus/util/TestSlotUtil.java b/plugin/src/test/java/ru/abstractmenus/util/TestSlotUtil.java similarity index 100% rename from src/test/java/ru/abstractmenus/util/TestSlotUtil.java rename to plugin/src/test/java/ru/abstractmenus/util/TestSlotUtil.java diff --git a/src/test/java/ru/abstractmenus/util/TestStringUtil.java b/plugin/src/test/java/ru/abstractmenus/util/TestStringUtil.java similarity index 100% rename from src/test/java/ru/abstractmenus/util/TestStringUtil.java rename to plugin/src/test/java/ru/abstractmenus/util/TestStringUtil.java diff --git a/src/test/java/ru/abstractmenus/util/TestTimeParser.java b/plugin/src/test/java/ru/abstractmenus/util/TestTimeParser.java similarity index 100% rename from src/test/java/ru/abstractmenus/util/TestTimeParser.java rename to plugin/src/test/java/ru/abstractmenus/util/TestTimeParser.java diff --git a/src/test/resources/test.conf b/plugin/src/test/resources/test.conf similarity index 100% rename from src/test/resources/test.conf rename to plugin/src/test/resources/test.conf diff --git a/settings.gradle b/settings.gradle index 127b032..610006e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,4 @@ -rootProject.name = 'AbstractMenus' \ No newline at end of file +rootProject.name = 'AbstractMenus' + +include 'api' +include 'plugin' \ No newline at end of file diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionMoneyGive.java b/src/main/java/ru/abstractmenus/data/actions/ActionMoneyGive.java deleted file mode 100644 index 7439f3a..0000000 --- a/src/main/java/ru/abstractmenus/data/actions/ActionMoneyGive.java +++ /dev/null @@ -1,37 +0,0 @@ -package ru.abstractmenus.data.actions; - - -import ru.abstractmenus.datatype.TypeDouble; -import ru.abstractmenus.hocon.api.ConfigNode; -import ru.abstractmenus.hocon.api.serialize.NodeSerializeException; -import ru.abstractmenus.hocon.api.serialize.NodeSerializer; -import org.bukkit.entity.Player; -import ru.abstractmenus.api.Action; -import ru.abstractmenus.api.inventory.Menu; -import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; - -public class ActionMoneyGive implements Action { - - private final TypeDouble money; - - private ActionMoneyGive(TypeDouble money) { - this.money = money; - } - - @Override - public void activate(Player player, Menu menu, Item clickedItem) { - if (Handlers.getEconomyHandler() != null) { - Handlers.getEconomyHandler().giveBalance(player, money.getDouble(player, menu)); - } - } - - public static class Serializer implements NodeSerializer { - - @Override - public ActionMoneyGive deserialize(Class type, ConfigNode node) throws NodeSerializeException { - return new ActionMoneyGive(node.getValue(TypeDouble.class)); - } - - } -} diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionMoneyTake.java b/src/main/java/ru/abstractmenus/data/actions/ActionMoneyTake.java deleted file mode 100644 index e5f92bf..0000000 --- a/src/main/java/ru/abstractmenus/data/actions/ActionMoneyTake.java +++ /dev/null @@ -1,37 +0,0 @@ -package ru.abstractmenus.data.actions; - - -import ru.abstractmenus.datatype.TypeDouble; -import ru.abstractmenus.hocon.api.ConfigNode; -import ru.abstractmenus.hocon.api.serialize.NodeSerializeException; -import ru.abstractmenus.hocon.api.serialize.NodeSerializer; -import org.bukkit.entity.Player; -import ru.abstractmenus.api.Action; -import ru.abstractmenus.api.inventory.Menu; -import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; - -public class ActionMoneyTake implements Action { - - private final TypeDouble money; - - private ActionMoneyTake(TypeDouble money) { - this.money = money; - } - - @Override - public void activate(Player player, Menu menu, Item clickedItem) { - if (Handlers.getEconomyHandler() != null) { - Handlers.getEconomyHandler().takeBalance(player, money.getDouble(player, menu)); - } - } - - public static class Serializer implements NodeSerializer { - - @Override - public ActionMoneyTake deserialize(Class type, ConfigNode node) throws NodeSerializeException { - return new ActionMoneyTake(node.getValue(TypeDouble.class)); - } - - } -} diff --git a/src/main/java/ru/abstractmenus/data/actions/MenuActions.java b/src/main/java/ru/abstractmenus/data/actions/MenuActions.java deleted file mode 100644 index f7fb1ff..0000000 --- a/src/main/java/ru/abstractmenus/data/actions/MenuActions.java +++ /dev/null @@ -1,93 +0,0 @@ -package ru.abstractmenus.data.actions; - -import ru.abstractmenus.api.Types; -import ru.abstractmenus.data.actions.var.*; -import ru.abstractmenus.data.actions.varp.*; -import ru.abstractmenus.data.actions.wrappers.ActionBulk; -import ru.abstractmenus.data.actions.wrappers.ActionDelay; -import ru.abstractmenus.data.actions.wrappers.ActionPlayerScope; -import ru.abstractmenus.data.actions.wrappers.ActionRandomActions; - -public final class MenuActions { - - private MenuActions() { - } - - public static void init() { - Types.registerAction("closeMenu", ActionMenuClose.class, new ActionMenuClose.Serializer()); - Types.registerAction("openMenu", ActionMenuOpen.class, new ActionMenuOpen.Serializer()); - Types.registerAction("openMenuCtx", ActionMenuOpenCtx.class, new ActionMenuOpenCtx.Serializer()); - Types.registerAction("playerScope", ActionPlayerScope.class, new ActionPlayerScope.Serializer()); - - Types.registerAction("addGroup", ActionGroupAdd.class, new ActionGroupAdd.Serializer()); - Types.registerAction("bungeeConnect", ActionBungeeConnect.class, new ActionBungeeConnect.Serializer()); - Types.registerAction("refreshMenu", ActionMenuRefresh.class, new ActionMenuRefresh.Serializer()); - Types.registerAction("command", ActionCommand.class, new ActionCommand.Serializer()); - Types.registerAction("giveLevel", ActionLevelGive.class, new ActionLevelGive.Serializer()); - Types.registerAction("giveMoney", ActionMoneyGive.class, new ActionMoneyGive.Serializer()); - Types.registerAction("givePermission", ActionPermissionGive.class, new ActionPermissionGive.Serializer()); - Types.registerAction("lpMetaSet", ActionLuckPermsMetaSet.class, new ActionLuckPermsMetaSet.Serializer()); - Types.registerAction("lpMetaRemove", ActionLuckPermsMetaRemove.class, new ActionLuckPermsMetaRemove.Serializer()); - Types.registerAction("giveXp", ActionXpGive.class, new ActionXpGive.Serializer()); - Types.registerAction("itemAdd", ActionItemAdd.class, new ActionItemAdd.Serializer()); - Types.registerAction("itemRemove", ActionItemRemove.class, new ActionItemRemove.Serializer()); - Types.registerAction("itemClear", ActionItemClear.class, new ActionItemClear.Serializer()); - Types.registerAction("inventoryClear", ActionInventoryClear.class, new ActionInventoryClear.Serializer()); - Types.registerAction("message", ActionMessage.class, new ActionMessage.Serializer()); - Types.registerAction("broadcast", ActionBroadcast.class, new ActionBroadcast.Serializer()); - Types.registerAction("miniMessage", ActionMiniMessage.class, new ActionMiniMessage.Serializer()); - Types.registerAction("potionEffect", ActionPotionEffect.class, new ActionPotionEffect.Serializer()); - Types.registerAction("removePotionEffect", ActionPotionEffectRemove.class, new ActionPotionEffectRemove.Serializer()); - Types.registerAction("removeGroup", ActionGroupRemove.class, new ActionGroupRemove.Serializer()); - Types.registerAction("removePermission", ActionPermissionRemove.class, new ActionPermissionRemove.Serializer()); - Types.registerAction("setFoodLevel", ActionFoodLevelSet.class, new ActionFoodLevelSet.Serializer()); - Types.registerAction("setHealth", ActionHealthSet.class, new ActionHealthSet.Serializer()); - Types.registerAction("sound", ActionSound.class, new ActionSound.Serializer()); - - try { - // SoundCategory missing on legacy Bukkit - Types.registerAction("customSound", ActionSoundCustom.class, new ActionSoundCustom.Serializer()); - } catch (Throwable ignore) { - } - - Types.registerAction("takeLevel", ActionLevelTake.class, new ActionLevelTake.Serializer()); - Types.registerAction("takeMoney", ActionMoneyTake.class, new ActionMoneyTake.Serializer()); - Types.registerAction("takeXp", ActionXpTake.class, new ActionXpTake.Serializer()); - Types.registerAction("teleport", ActionTeleport.class, new ActionTeleport.Serializer()); - Types.registerAction("openBook", ActionBookOpen.class, new ActionBookOpen.Serializer()); - Types.registerAction("delay", ActionDelay.class, new ActionDelay.Serializer()); - Types.registerAction("setSkin", ActionSkinSet.class, new ActionSkinSet.Serializer()); - Types.registerAction("resetSkin", ActionSkinReset.class, new ActionSkinReset.Serializer()); - Types.registerAction("addRecipe", ActionRecipeAdd.class, new ActionRecipeAdd.Serializer()); - Types.registerAction("pageNext", ActionPageNext.class, new ActionPageNext.Serializer()); - Types.registerAction("pagePrev", ActionPagePrev.class, new ActionPagePrev.Serializer()); - Types.registerAction("bulk", ActionBulk.class, new ActionBulk.Serializer()); - Types.registerAction("setProperty", ActionPropertySet.class, new ActionPropertySet.Serializer()); - Types.registerAction("remProperty", ActionPropertyRemove.class, new ActionPropertyRemove.Serializer()); - Types.registerAction("refreshItem", ActionItemRefresh.class, new ActionItemRefresh.Serializer()); - Types.registerAction("randActions", ActionRandomActions.class, new ActionRandomActions.Serializer()); - Types.registerAction("playerChat", ActionPlayerChat.class, new ActionPlayerChat.Serializer()); - Types.registerAction("setGamemode", ActionGameModeSet.class, new ActionGameModeSet.Serializer()); - Types.registerAction("inputChat", ActionInputChat.class, new ActionInputChat.Serializer()); - Types.registerAction("setButton", ActionButtonSet.class, new ActionButtonSet.Serializer()); - Types.registerAction("removeButton", ActionButtonRemove.class, new ActionButtonRemove.Serializer()); - Types.registerAction("removePlaced", ActionPlacedItemRemove.class, new ActionPlacedItemRemove.Serializer()); - Types.registerAction("placeItem", ActionPlaceItem.class, new ActionPlaceItem.Serializer()); - - Types.registerAction("setVar", ActionVarSet.class, new ActionVarSet.Serializer()); - Types.registerAction("removeVar", ActionVarRem.class, new ActionVarRem.Serializer()); - Types.registerAction("incVar", ActionVarInc.class, new ActionVarInc.Serializer()); - Types.registerAction("decVar", ActionVarDec.class, new ActionVarDec.Serializer()); - Types.registerAction("mulVar", ActionVarMul.class, new ActionVarMul.Serializer()); - Types.registerAction("divVar", ActionVarDiv.class, new ActionVarDiv.Serializer()); - - Types.registerAction("setVarp", ActionVarpSet.class, new ActionVarpSet.Serializer()); - Types.registerAction("removeVarp", ActionVarpRem.class, new ActionVarpRem.Serializer()); - Types.registerAction("incVarp", ActionVarpInc.class, new ActionVarpInc.Serializer()); - Types.registerAction("decVarp", ActionVarpDec.class, new ActionVarpDec.Serializer()); - Types.registerAction("mulVarp", ActionVarpMul.class, new ActionVarpMul.Serializer()); - Types.registerAction("divVarp", ActionVarpDiv.class, new ActionVarpDiv.Serializer()); - - Types.registerAction("print", ActionLog.class, new ActionLog.Serializer()); - } -} diff --git a/src/main/java/ru/abstractmenus/data/activators/Activators.java b/src/main/java/ru/abstractmenus/data/activators/Activators.java deleted file mode 100644 index 95d973e..0000000 --- a/src/main/java/ru/abstractmenus/data/activators/Activators.java +++ /dev/null @@ -1,37 +0,0 @@ -package ru.abstractmenus.data.activators; - -import ru.abstractmenus.AbstractMenus; -import ru.abstractmenus.api.Types; - -public final class Activators { - - private Activators() { - } - - public static void init() { - Types.registerActivator("command", OpenCommand.class, new OpenCommand.Serializer()); - Types.registerActivator("chat", OpenChat.class, new OpenChat.Serializer()); - Types.registerActivator("containsChat", OpenChatContains.class, new OpenChatContains.Serializer()); - Types.registerActivator("join", OpenJoin.class, new OpenJoin.Serializer()); - Types.registerActivator("clickEntity", OpenClickEntity.class, new OpenClickEntity.Serializer()); - Types.registerActivator("shiftClickEntity", OpenShiftClickEntity.class, new OpenShiftClickEntity.Serializer()); - Types.registerActivator("clickItem", OpenClickItem.class, new OpenClickItem.Serializer()); - Types.registerActivator("button", OpenButton.class, new OpenButton.Serializer()); - Types.registerActivator("lever", OpenLever.class, new OpenLever.Serializer()); - Types.registerActivator("plate", OpenPlate.class, new OpenPlate.Serializer()); - Types.registerActivator("table", OpenSign.class, new OpenSign.Serializer()); - Types.registerActivator("clickBlock", OpenClickBlock.class, new OpenClickBlock.Serializer()); - Types.registerActivator("clickBlockType", OpenClickBlockType.class, new OpenClickBlockType.Serializer()); - Types.registerActivator("swapItems", OpenSwapItems.class, new OpenSwapItems.Serializer()); - - if (AbstractMenus.checkDependency("Citizens")) { - Types.registerActivator("clickNPC", OpenClickNPC.class, new OpenClickNPC.Serializer()); - } - - if (AbstractMenus.checkDependency("WorldGuard")) { - Types.registerActivator("regionJoin", OpenRegionEnter.class, new OpenRegionEnter.Serializer()); - Types.registerActivator("regionLeave", OpenRegionLeave.class, new OpenRegionLeave.Serializer()); - } - } - -} diff --git a/src/main/java/ru/abstractmenus/data/catalogs/Catalogs.java b/src/main/java/ru/abstractmenus/data/catalogs/Catalogs.java deleted file mode 100644 index e73a3d9..0000000 --- a/src/main/java/ru/abstractmenus/data/catalogs/Catalogs.java +++ /dev/null @@ -1,18 +0,0 @@ -package ru.abstractmenus.data.catalogs; - -import ru.abstractmenus.api.Types; - -public final class Catalogs { - - private Catalogs(){} - - public static void init() { - Types.registerCatalog("iterator", IteratorCatalog.class, new IteratorCatalog.Serializer()); - Types.registerCatalog("players", PlayerCatalog.class, new PlayerCatalog.Serializer()); - Types.registerCatalog("entities", EntityCatalog.class, new EntityCatalog.Serializer()); - Types.registerCatalog("worlds", WorldCatalog.class, new WorldCatalog.Serializer()); - Types.registerCatalog("bungee_servers", ServerCatalog.class, new ServerCatalog.Serializer()); - Types.registerCatalog("slice", SliceCatalog.class, new SliceCatalog.Serializer()); - } - -} diff --git a/src/main/java/ru/abstractmenus/data/properties/ItemProps.java b/src/main/java/ru/abstractmenus/data/properties/ItemProps.java deleted file mode 100644 index 4bf9720..0000000 --- a/src/main/java/ru/abstractmenus/data/properties/ItemProps.java +++ /dev/null @@ -1,47 +0,0 @@ -package ru.abstractmenus.data.properties; - -import ru.abstractmenus.api.Types; - -public final class ItemProps { - - public static final String BINDINGS = "bindings"; - - public static void init() { - Types.registerItemProperty("material", PropMaterial.class, new PropMaterial.Serializer()); - Types.registerItemProperty("texture", PropTexture.class, new PropTexture.Serializer()); - Types.registerItemProperty("skullOwner", PropSkullOwner.class, new PropSkullOwner.Serializer()); - Types.registerItemProperty("hdb", PropHDB.class, new PropHDB.Serializer()); - Types.registerItemProperty("mmoitem", PropMmoItem.class, new PropMmoItem.Serializer()); - Types.registerItemProperty("itemsAdder", PropItemsAdder.class, new PropItemsAdder.Serializer()); - Types.registerItemProperty("oraxen", PropOraxen.class, new PropOraxen.Serializer()); - Types.registerItemProperty("equipItem", PropEquipItem.class, new PropEquipItem.Serializer()); - Types.registerItemProperty("serialized", PropSerialized.class, new PropSerialized.Serializer()); - - Types.registerItemProperty("name", PropName.class, new PropName.Serializer()); - Types.registerItemProperty("data", PropData.class, new PropData.Serializer()); - Types.registerItemProperty("count", PropCount.class, new PropCount.Serializer()); - Types.registerItemProperty("lore", PropLore.class, new PropLore.Serializer()); - Types.registerItemProperty("glow", PropGlow.class, new PropGlow.Serializer()); - Types.registerItemProperty("enchantments", PropEnchantments.class, new PropEnchantments.Serializer()); - Types.registerItemProperty("color", PropColor.class, new PropColor.Serializer()); - Types.registerItemProperty("flags", PropFlags.class, new PropFlags.Serializer()); - Types.registerItemProperty("unbreakable", PropUnbreakable.class, new PropUnbreakable.Serializer()); - Types.registerItemProperty("potionData", PropPotionData.class, new PropPotionData.Serializer()); - Types.registerItemProperty("fireworkData", PropFireworkData.class, new PropFireworkData.Serializer()); - Types.registerItemProperty("attributeModifier", PropAttributeModifier.class, new PropAttributeModifier.Serializer()); - Types.registerItemProperty("bookData", PropBookData.class, new PropBookData.Serializer()); - Types.registerItemProperty("bannerData", PropBannerData.class, new PropBannerData.Serializer()); - Types.registerItemProperty("shieldData", PropShieldData.class, new PropShieldData.Serializer()); - Types.registerItemProperty("model", PropModel.class, new PropModel.Serializer()); - Types.registerItemProperty("enchantStore", PropEnchantStore.class, new PropEnchantStore.Serializer()); - Types.registerItemProperty("recipes", PropKnowledgeBook.class, new PropKnowledgeBook.Serializer()); - Types.registerItemProperty("damage", PropDamage.class, new PropDamage.Serializer()); - Types.registerItemProperty("nbt", PropNbt.class, new PropNbt.Serializer()); - Types.registerItemProperty(BINDINGS, PropBindings.class, new PropBindings.Serializer()); - - Types.registerItemProperty("nameLight", PropNameLight.class, new PropNameLight.Serializer()); - Types.registerItemProperty("loreLight", PropLoreLight.class, new PropLoreLight.Serializer()); - } - - private ItemProps(){} -} diff --git a/src/main/java/ru/abstractmenus/data/rules/MenuRules.java b/src/main/java/ru/abstractmenus/data/rules/MenuRules.java deleted file mode 100644 index 389f8dc..0000000 --- a/src/main/java/ru/abstractmenus/data/rules/MenuRules.java +++ /dev/null @@ -1,46 +0,0 @@ -package ru.abstractmenus.data.rules; - - -import ru.abstractmenus.api.Types; -import ru.abstractmenus.data.rules.logical.RuleOneOf; -import ru.abstractmenus.data.rules.logical.RuleOr; -import ru.abstractmenus.data.rules.logical.RuleAnd; -import ru.abstractmenus.data.rules.logical.RulePlayerScope; - -public final class MenuRules { - - private MenuRules(){} - - public static void init(){ - Types.registerRule("chance", RuleChance.class, new RuleChance.Serializer()); - Types.registerRule("if", RuleIf.class, new RuleIf.Serializer()); - Types.registerRule("foodLevel", RuleFoodLevel.class, new RuleFoodLevel.Serializer()); - Types.registerRule("gamemode", RuleGameMode.class, new RuleGameMode.Serializer()); - Types.registerRule("group", RuleGroup.class, new RuleGroup.Serializer()); - Types.registerRule("health", RuleHealth.class, new RuleHealth.Serializer()); - Types.registerRule("inventoryItems", RuleInventoryItem.class, new RuleInventoryItem.Serializer()); - Types.registerRule("heldItem", RuleHeldItem.class, new RuleHeldItem.Serializer()); - Types.registerRule("level", RuleLevel.class, new RuleLevel.Serializer()); - Types.registerRule("money", RuleMoney.class, new RuleMoney.Serializer()); - Types.registerRule("online", RuleOnline.class, new RuleOnline.Serializer()); - Types.registerRule("bungeeOnline", RuleBungeeOnline.class, new RuleBungeeOnline.Serializer()); - Types.registerRule("permission", RulePermission.class, new RulePermission.Serializer()); - Types.registerRule("world", RuleWorld.class, new RuleWorld.Serializer()); - Types.registerRule("xp", RuleXp.class, new RuleXp.Serializer()); - Types.registerRule("existVar", RuleExistVar.class, new RuleExistVar.Serializer()); - Types.registerRule("existVarp", RuleExistVarp.class, new RuleExistVarp.Serializer()); - Types.registerRule("freeSlot", RuleFreeSlot.class, new RuleFreeSlot.Serializer()); - Types.registerRule("freeSlotCount", RuleFreeSlotCount.class, new RuleFreeSlotCount.Serializer()); - Types.registerRule("region", RuleRegion.class, new RuleRegion.Serializer()); - Types.registerRule("js", RuleJS.class, new RuleJS.Serializer()); - Types.registerRule("bungeeIsOnline", RuleBungeeIsOnline.class, new RuleBungeeIsOnline.Serializer()); - Types.registerRule("playerIsOnline", RulePlayerIsOnline.class, new RulePlayerIsOnline.Serializer()); - Types.registerRule("placedItem", RulePlacedItem.class, new RulePlacedItem.Serializer()); - - Types.registerRule("and", RuleAnd.class, new RuleAnd.Serializer()); - Types.registerRule("or", RuleOr.class, new RuleOr.Serializer()); - Types.registerRule("oneof", RuleOneOf.class, new RuleOneOf.Serializer()); - Types.registerRule("playerScope", RulePlayerScope.class, new RulePlayerScope.Serializer()); - } - -} diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleMoney.java b/src/main/java/ru/abstractmenus/data/rules/RuleMoney.java deleted file mode 100644 index ca73a5d..0000000 --- a/src/main/java/ru/abstractmenus/data/rules/RuleMoney.java +++ /dev/null @@ -1,37 +0,0 @@ -package ru.abstractmenus.data.rules; - -import ru.abstractmenus.hocon.api.ConfigNode; -import ru.abstractmenus.hocon.api.serialize.NodeSerializeException; -import ru.abstractmenus.hocon.api.serialize.NodeSerializer; -import org.bukkit.entity.Player; -import ru.abstractmenus.api.Rule; -import ru.abstractmenus.api.inventory.Menu; -import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; -import ru.abstractmenus.datatype.TypeDouble; - -public class RuleMoney implements Rule { - - private final TypeDouble money; - - private RuleMoney(TypeDouble money){ - this.money = money; - } - - @Override - public boolean check(Player player, Menu menu, Item clickedItem) { - if(Handlers.getEconomyHandler() != null){ - return Handlers.getEconomyHandler().hasBalance(player, money.getDouble(player, menu)); - } - return false; - } - - public static class Serializer implements NodeSerializer { - - @Override - public RuleMoney deserialize(Class type, ConfigNode node) throws NodeSerializeException { - return new RuleMoney(node.getValue(TypeDouble.class)); - } - - } -}