From f6f08cf4d05265d025620de4fa49244748dd0875 Mon Sep 17 00:00:00 2001 From: 1robie Date: Tue, 13 Jan 2026 23:07:15 +0100 Subject: [PATCH 01/56] feat: implement title animation functionality and related classes --- AGENTS.md | 131 +++++++++++++++++ API/build.gradle.kts | 4 + .../java/fr/maxlego08/menu/api/Inventory.java | 5 + .../fr/maxlego08/menu/api/MenuPlugin.java | 2 + .../menu/api/TitleAnimationManager.java | 15 ++ .../api/animation/PlayerTitleAnimation.java | 133 ++++++++++++++++++ .../menu/api/animation/TitleAnimation.java | 9 ++ .../api/animation/TitleAnimationLoader.java | 39 +++++ .../api/animation/TitleAnimationSettings.java | 17 +++ .../menu/api/engine/BaseInventory.java | 12 ++ .../menu/api/utils/PaperMetaUpdater.java | 9 ++ Common/build.gradle.kts | 14 ++ .../hooks/packetevents/PacketEventLoader.java | 4 + .../hooks/packetevents/PacketListener.java | 6 +- .../menu/hooks/packetevents/PacketUtils.java | 6 +- .../animation/PacketPlayerTitleAnimation.java | 45 ++++++ .../animation/PacketTitleAnimation.java | 20 +++ .../listener/PacketAnimationListener.java | 119 ++++++++++++++++ .../PacketEventTitleAnimationLoader.java | 16 +++ Hooks/Paper/build.gradle.kts | 2 +- .../src/main/java/fr/maxlego08/menu/Test.java | 10 ++ .../maxlego08/menu/hooks/ComponentMeta.java | 9 +- Hooks/build.gradle.kts | 2 + build.gradle.kts | 3 +- instruction.md | 48 +++++++ opencode.json | 14 ++ .../java/fr/maxlego08/menu/ZInventory.java | 12 ++ .../java/fr/maxlego08/menu/ZMenuPlugin.java | 24 +++- .../menu/ZTitleAnimationManager.java | 35 +++++ .../maxlego08/menu/inventory/VInventory.java | 27 ++++ .../inventories/InventoryDefault.java | 1 + .../menu/loader/InventoryLoader.java | 20 +++ .../menu/zcore/utils/plugins/Plugins.java | 21 ++- 33 files changed, 818 insertions(+), 16 deletions(-) create mode 100644 AGENTS.md create mode 100644 API/src/main/java/fr/maxlego08/menu/api/TitleAnimationManager.java create mode 100644 API/src/main/java/fr/maxlego08/menu/api/animation/PlayerTitleAnimation.java create mode 100644 API/src/main/java/fr/maxlego08/menu/api/animation/TitleAnimation.java create mode 100644 API/src/main/java/fr/maxlego08/menu/api/animation/TitleAnimationLoader.java create mode 100644 API/src/main/java/fr/maxlego08/menu/api/animation/TitleAnimationSettings.java create mode 100644 API/src/main/java/fr/maxlego08/menu/api/utils/PaperMetaUpdater.java create mode 100644 Common/build.gradle.kts create mode 100644 Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/PacketEventLoader.java create mode 100644 Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/animation/PacketPlayerTitleAnimation.java create mode 100644 Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/animation/PacketTitleAnimation.java create mode 100644 Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/listener/PacketAnimationListener.java create mode 100644 Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/loader/PacketEventTitleAnimationLoader.java create mode 100644 Hooks/Paper/src/main/java/fr/maxlego08/menu/Test.java create mode 100644 instruction.md create mode 100644 opencode.json create mode 100644 src/main/java/fr/maxlego08/menu/ZTitleAnimationManager.java diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..141bcd37 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,131 @@ +# AGENTS.md + +> **Agentic Automation & Development Guide** +> zMenu / GroupeZ-dev + +--- + +**Quick Reference Links:** +- [Documentation](https://docs.zmenu.dev/) +- [API Javadocs](https://javadocs.groupez.dev/zmenu) +- [Discord Support](https://discord.groupez.dev/) +- [README](./README.md) +- [Changelog](./changelog.md) + +--- + +## 📦 Build, Lint, and Test Commands + +| Task | Gradle Command | Notes | +|------------------------|--------------------------------------|---------------------------------------------------| +| Build all | `./gradlew build` | Output: `target/`, API jars: `target-api/` | +| Lint (CI) | _Super-Linter in GitHub Actions_ | On PRs/push to `main`, `develop` | +| Test all | `./gradlew test` | JUnit 5, discovers test files by convention | +| Test single class | `./gradlew test --tests '*ClassName'`| Use fully qualified names as needed | +| Test single method | `./gradlew test --tests '*ClassName.methodName'` | Regexp/wildcard match supported | +| Full clean | `./gradlew clean build` | Removes build artifacts first | + +**CI/Automation Lint:** +- Linting runs via GitHub Actions: `super-linter`. +- Enforced on all pushes/PRs to protected branches. +- Locally, linting and formatting must match `.editorconfig` and IntelliJ IDE. + +**Dependency/Gradle Guidance:** +- Strictly define all dependencies/plugins in `build.gradle.kts`. **Never** edit lock files by hand. +- Default toolchain: **Java 21** (see `toolchain` block). +- When adding dependencies, follow indentation and style of the root config. +- Output is produced in `target/` and `target-api/` folders. + +**Troubleshooting:** +- Ensure you use the provided `gradlew`/`gradlew.bat` scripts, **not** system Gradle. +- Line-length, indentation, or import order are the most common causes of build/lint failures. + +**Test Discovery:** +- JUnit test classes should be placed in the standard `src/test/java` directories within each module, named with `*Test.java` or `*Tests.java` by convention. + +--- + +## 🎨 Code Style, Formatting, and Project Conventions + +> Agents must strictly auto-correct all code style violations before merge or patch submission. + +### General Formatting: +- **Spaces only** (`indent_style = space`), **4 spaces for Java**, **2 for YAML** (`indent_size`). +- **Line length:** max **160** chars (`max_line_length`). +- **Encoding:** UTF-8. End-of-line: unset/IDE default. No required final newline. +- **Always** use 4-space indentation for Java (see `.editorconfig` `[*.java]`), 2-space for YAML. +- **Blank lines:** Keep two between top-level elements, and one after imports/package statements. +- **IntelliJ auto-format** must be respected (`ij_*` settings in `.editorconfig` control detailed wrapping, alignment, etc). + +### Imports: +- IntelliJ style: All wildcard imports (`@*`), static imports (`*`), then `javax.**`, `java.**`, then user packages (`$*`), with one blank line between groups. +- Do **not** use import wildcards unless >5 imports from the same package. +- Place imports after `package` declarations, before class definition. +- No imports for inner classes unless specifically required. + +### Java Indentation, Wrapping, & Structure (from `.editorconfig`): +- Braces style: **end_of_line** (Egyptian braces: `{` on same line) +- Wrapping: Most methods, params, and arrays: do **not** wrap by default. Builder/annotation/params: wrap before elements, not after. +- Case/switch: indent by one level, case on separate line. +- Import/order/blank lines: See `ij_java_*` rules for precise handling. +- YAML files: indent with 2 spaces (see `[*.yml,*.yaml]`). + +### Javadoc & Comments: +- Use `/** ... */` for Javadoc on **all public classes/methods**. +- Align params, throws, and returns vertically. Always add a blank after the description. +- Block/line comments: Space after `//`, align at column start. + +### Naming Conventions: +- Classes: PascalCase. Methods/vars: camelCase. Constants: UPPER_SNAKE_CASE. Interfaces: end with -able/-Listener or semantic (`FooListener`), else PascalCase. +- Packages: all lowercase, dot-separated. Resources/configs: kebab-case, lower-case extensions. + +### Spacing & Operator Rules: +- Always put spaces after commas/semicolons; before `{`; around `=`, `+`, etc. +- **No** space between method name and parenthesis: `foo(bar)`, not `foo (bar)`. +- No extra spaces inside parentheses. + +### Other Style Essentials: +- Arrays/lists: no newline after `{`, one entry per line unless >80 chars. +- “Widget/annotation/parameter” lists: split lines before each element if multiline. +- Do not enforce a final newline in code or config files. +- Use `.editorconfig` and reformat via IDE or script before reviews. + +--- + +## 📋 Error Handling & Logging +- **Always** check for null unless contractually guaranteed. +- Prefer granular `try/catch` blocks—provide **full tracebacks** for errors. +- **Never** `System.out.println`—use `fr.maxlego08.menu.zcore.logger.Logger` error/info methods. +- Log **all** exceptions at error level, with useful context, not just stack. +- For all command/argument validation, employ validation helpers and user-facing feedback. + +--- + +## 🛠 Project Architecture Guidelines +- Multi-module: key folders are `API/`, `Common/`, and `Hooks/`. +- Shared logic goes in `Common`; API contracts and interfaces in `API`; integrations in `Hooks`. +- **No cross-module imports** except by well-defined APIs. +- Plugins/libraries must follow `build.gradle.kts` group/versioning style. +- **Java 21** required (Gradle enforced). +- Test code: keep *only* in designated test source roots. + +--- + +## 🕵️‍♂️ Agent Protocols & Good Practices +- Run `./gradlew build` and `./gradlew test` prior to PR or merge. +- Run **specific test** before pushing: `./gradlew test --tests '*ClassName.methodName'`. +- If touching dependencies, **update version** and add a changelog entry. +- Reference official docs/readme/JavaDocs for context! See top links. +- **Respect modular boundaries:** Do not blur API/adapters/core code. +- **Commit messages:** clear and intent-driven (e.g. `fix: ...`, `feat: ...`, `refactor: ...`). +- **Prior to merge:** Ensure auto-format ({IDE} or script) matches `.editorconfig`. +- **If no AGENTS.md:** You must maintain and update this file after material changes! + +## For More Info +- [Documentation](https://docs.zmenu.dev/) +- [API Javadocs](https://javadocs.groupez.dev/zmenu) +- [Discord (support)](https://discord.groupez.dev/) + +--- + +**ALL AGENTS** must enforce these rules before checking in code, running PRs, or post-processing patches. Lint, test, and *never* introduce manual code style drift. Reference and update this file regularly! \ No newline at end of file diff --git a/API/build.gradle.kts b/API/build.gradle.kts index 9c28f817..c4ee9f05 100644 --- a/API/build.gradle.kts +++ b/API/build.gradle.kts @@ -2,6 +2,10 @@ plugins { id("re.alwyn974.groupez.publish") version "1.0.0" } +dependencies{ + implementation("net.kyori:adventure-api:4.25.0") +} + rootProject.extra.properties["sha"]?.let { sha -> version = sha } diff --git a/API/src/main/java/fr/maxlego08/menu/api/Inventory.java b/API/src/main/java/fr/maxlego08/menu/api/Inventory.java index c43b12b1..c00a1e79 100644 --- a/API/src/main/java/fr/maxlego08/menu/api/Inventory.java +++ b/API/src/main/java/fr/maxlego08/menu/api/Inventory.java @@ -1,5 +1,6 @@ package fr.maxlego08.menu.api; +import fr.maxlego08.menu.api.animation.TitleAnimation; import fr.maxlego08.menu.api.button.Button; import fr.maxlego08.menu.api.engine.InventoryEngine; import fr.maxlego08.menu.api.engine.InventoryResult; @@ -216,4 +217,8 @@ public interface Inventory { * @return The player name placeholder. */ String getTargetPlayerNamePlaceholder(); + + void setTitleAnimation(TitleAnimation load); + + TitleAnimation getTitleAnimation(); } \ No newline at end of file diff --git a/API/src/main/java/fr/maxlego08/menu/api/MenuPlugin.java b/API/src/main/java/fr/maxlego08/menu/api/MenuPlugin.java index 412eb51c..f19c4270 100644 --- a/API/src/main/java/fr/maxlego08/menu/api/MenuPlugin.java +++ b/API/src/main/java/fr/maxlego08/menu/api/MenuPlugin.java @@ -251,4 +251,6 @@ public interface MenuPlugin extends Plugin { ItemManager getItemManager(); AttributApplier getAttributApplier(); + + TitleAnimationManager getTitleAnimationManager(); } diff --git a/API/src/main/java/fr/maxlego08/menu/api/TitleAnimationManager.java b/API/src/main/java/fr/maxlego08/menu/api/TitleAnimationManager.java new file mode 100644 index 00000000..9246f74b --- /dev/null +++ b/API/src/main/java/fr/maxlego08/menu/api/TitleAnimationManager.java @@ -0,0 +1,15 @@ +package fr.maxlego08.menu.api; + +import fr.maxlego08.menu.api.animation.TitleAnimationLoader; +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; + +public interface TitleAnimationManager { + + boolean registerLoader(@NotNull String id, @NotNull TitleAnimationLoader loader); + + Optional getLoader(@NotNull String id); + + Optional getFirstLoader(); +} diff --git a/API/src/main/java/fr/maxlego08/menu/api/animation/PlayerTitleAnimation.java b/API/src/main/java/fr/maxlego08/menu/api/animation/PlayerTitleAnimation.java new file mode 100644 index 00000000..807312e8 --- /dev/null +++ b/API/src/main/java/fr/maxlego08/menu/api/animation/PlayerTitleAnimation.java @@ -0,0 +1,133 @@ +package fr.maxlego08.menu.api.animation; + +import com.tcoded.folialib.wrapper.task.WrappedTask; +import fr.maxlego08.menu.api.MenuPlugin; +import fr.maxlego08.menu.api.utils.PaperMetaUpdater; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +@SuppressWarnings("unused") +public abstract class PlayerTitleAnimation { + private final MenuPlugin plugin; + protected final int inventoryId; + protected PaperMetaUpdater metaUpdater; + + protected final InventoryType inventoryType; + protected final int size; + + protected final TitleAnimationSettings settings; + + protected int currentIndex = 0; + protected int currentCycle = 0; + protected boolean isStarted = false; + + protected WrappedTask wrappedTask; + + protected PlayerTitleAnimation(@NotNull MenuPlugin plugin,@NotNull TitleAnimationSettings settings, int inventoryId, InventoryType type, int size) { + this.plugin = plugin; + this.settings = settings; + this.inventoryId = inventoryId; + this.inventoryType = type; + this.size = size; + if (!(this.plugin.getMetaUpdater() instanceof PaperMetaUpdater paperMetaUpdater)) { + throw new UnsupportedOperationException("Title animations are only supported on Paper servers."); + } + this.metaUpdater = paperMetaUpdater; + } + + public void start(@NotNull Player player,@NotNull List inventoryContents){ + if (this.settings.titles().isEmpty()) { + return; + } + this.currentCycle = 0; + this.currentIndex = 0; + this.wrappedTask = this.plugin.getScheduler().runAtEntityTimer(player, () -> { + if (this.currentIndex >= this.settings.titles().size()) { + this.currentIndex = 0; + this.currentCycle++; + if (this.settings.cycles() > 0 && this.currentCycle >= this.settings.cycles()) { + if (this.settings.showItemsAfterAnimation()) { + this.sendInventoryContent(player, inventoryContents); + } + this.stop(); + return; + } + } + String title = this.settings.titles().get(this.currentIndex++); + this.sendTitle(player, title); + if (!this.settings.showItemsAfterAnimation()) { + if (this.settings.itemUpdateInterval() > 0 && this.currentIndex % this.settings.itemUpdateInterval() == 0) { + this.sendInventoryContent(player, inventoryContents); + } + } + }, this.settings.initialDelay(), this.settings.interval(), this.settings.timeUnit()); + this.isStarted = true; + } + + public void stop(){ + if(this.wrappedTask != null){ + this.wrappedTask.cancel(); + } + this.isStarted = false; + this.currentIndex = 0; + } + + public abstract void sendTitle(@NotNull Player player,@NotNull String title); + + public abstract void sendInventoryContent(@NotNull Player player, @NotNull List inventoryContents); + + public int getCurrentIndex() { + return this.currentIndex; + } + + @Contract(pure = true) + public @NotNull MenuPlugin getPlugin() { + return this.plugin; + } + + @Contract(pure = true) + public int getInventoryId() { + return this.inventoryId; + } + + @Contract(pure = true) + public @NotNull PaperMetaUpdater getMetaUpdater() { + return this.metaUpdater; + } + + @Contract(pure = true) + public @NotNull InventoryType getInventoryType() { + return this.inventoryType; + } + + @Contract(pure = true) + public int getSize() { + return this.size; + } + + @Contract(pure = true) + public @NotNull TitleAnimationSettings getSettings() { + return this.settings; + } + + @Contract(pure = true) + public int getCurrentCycle() { + return this.currentCycle; + } + + @Contract(pure = true) + public boolean isStarted() { + return this.isStarted; + } + + @Contract(pure = true) + public @Nullable WrappedTask getWrappedTask() { + return this.wrappedTask; + } +} diff --git a/API/src/main/java/fr/maxlego08/menu/api/animation/TitleAnimation.java b/API/src/main/java/fr/maxlego08/menu/api/animation/TitleAnimation.java new file mode 100644 index 00000000..69006425 --- /dev/null +++ b/API/src/main/java/fr/maxlego08/menu/api/animation/TitleAnimation.java @@ -0,0 +1,9 @@ +package fr.maxlego08.menu.api.animation; + +import fr.maxlego08.menu.api.MenuPlugin; +import org.bukkit.event.inventory.InventoryType; + +public interface TitleAnimation { + + PlayerTitleAnimation playTitleAnimation(MenuPlugin plugin, int containerId, InventoryType type, int size, Object... args); +} diff --git a/API/src/main/java/fr/maxlego08/menu/api/animation/TitleAnimationLoader.java b/API/src/main/java/fr/maxlego08/menu/api/animation/TitleAnimationLoader.java new file mode 100644 index 00000000..e826a151 --- /dev/null +++ b/API/src/main/java/fr/maxlego08/menu/api/animation/TitleAnimationLoader.java @@ -0,0 +1,39 @@ +package fr.maxlego08.menu.api.animation; + +import fr.maxlego08.menu.api.utils.Loader; +import org.bukkit.configuration.file.YamlConfiguration; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +public abstract class TitleAnimationLoader implements Loader { + protected List titles = new ArrayList<>(); + protected TimeUnit timeUnit = TimeUnit.SECONDS; + protected int initialDelay = 20; + protected int interval = 20; + protected int cycles = -1; + protected boolean showItemsAfterAnimation = false; + protected int itemUpdateInterval = 1; + + protected TitleAnimationSettings loadSettings(@NotNull YamlConfiguration configuration, @NotNull String path){ + this.titles = configuration.getStringList(path + "titles"); + this.cycles = configuration.getInt(path+"cycles",-1); + this.initialDelay = configuration.getInt(path+"initial-delay",20); + this.interval = configuration.getInt(path+"interval",20); + this.showItemsAfterAnimation = configuration.getBoolean(path + "show-items-after-animation", false); + this.itemUpdateInterval = configuration.getInt(path + "item-update-interval", 1); + try { + this.timeUnit = TimeUnit.valueOf(configuration.getString(path + "time-unit", "SECONDS").toUpperCase()); + } catch (IllegalArgumentException e) { + this.timeUnit = TimeUnit.SECONDS; + } + return new TitleAnimationSettings(this.titles,this.cycles, this.initialDelay, this.interval, this.timeUnit, this.showItemsAfterAnimation, this.itemUpdateInterval); + } + + @Override + public void save(TitleAnimation object, @NotNull YamlConfiguration configuration, @NotNull String path, File file, Object... objects) { + } +} diff --git a/API/src/main/java/fr/maxlego08/menu/api/animation/TitleAnimationSettings.java b/API/src/main/java/fr/maxlego08/menu/api/animation/TitleAnimationSettings.java new file mode 100644 index 00000000..4aec2efc --- /dev/null +++ b/API/src/main/java/fr/maxlego08/menu/api/animation/TitleAnimationSettings.java @@ -0,0 +1,17 @@ +package fr.maxlego08.menu.api.animation; + +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +public record TitleAnimationSettings( + List<@NotNull String> titles, + int cycles, + int initialDelay, + int interval, + @NotNull TimeUnit timeUnit, + boolean showItemsAfterAnimation, + int itemUpdateInterval +) { +} diff --git a/API/src/main/java/fr/maxlego08/menu/api/engine/BaseInventory.java b/API/src/main/java/fr/maxlego08/menu/api/engine/BaseInventory.java index 41a2f399..72220067 100644 --- a/API/src/main/java/fr/maxlego08/menu/api/engine/BaseInventory.java +++ b/API/src/main/java/fr/maxlego08/menu/api/engine/BaseInventory.java @@ -1,6 +1,8 @@ package fr.maxlego08.menu.api.engine; import fr.maxlego08.menu.api.MenuPlugin; +import fr.maxlego08.menu.api.animation.PlayerTitleAnimation; +import fr.maxlego08.menu.api.animation.TitleAnimation; import org.bukkit.entity.Player; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryHolder; @@ -81,4 +83,14 @@ public interface BaseInventory extends InventoryHolder { boolean isDisablePlayerInventoryClick(); void setDisablePlayerInventoryClick(boolean disablePlayerInventoryClick); + + void setPlayerTitleAnimation(PlayerTitleAnimation playerTitleAnimation); + + @Nullable + PlayerTitleAnimation getPlayerTitleAnimation(); + + void setTitleAnimation(TitleAnimation animation); + + TitleAnimation getTitleAnimation(); + } diff --git a/API/src/main/java/fr/maxlego08/menu/api/utils/PaperMetaUpdater.java b/API/src/main/java/fr/maxlego08/menu/api/utils/PaperMetaUpdater.java new file mode 100644 index 00000000..fb847e3b --- /dev/null +++ b/API/src/main/java/fr/maxlego08/menu/api/utils/PaperMetaUpdater.java @@ -0,0 +1,9 @@ +package fr.maxlego08.menu.api.utils; + +import net.kyori.adventure.text.Component; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface PaperMetaUpdater extends MetaUpdater { + @NotNull Component getComponent(@Nullable String text); +} diff --git a/Common/build.gradle.kts b/Common/build.gradle.kts new file mode 100644 index 00000000..ccc36266 --- /dev/null +++ b/Common/build.gradle.kts @@ -0,0 +1,14 @@ +group = "fr.maxlego08.menu" +version = "1.1.0.7" + +dependencies { + api(projects.api) + + testImplementation(platform("org.junit:junit-bom:5.10.0")) + testImplementation("org.junit.jupiter:junit-jupiter") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") +} + +tasks.test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/PacketEventLoader.java b/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/PacketEventLoader.java new file mode 100644 index 00000000..dde200b1 --- /dev/null +++ b/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/PacketEventLoader.java @@ -0,0 +1,4 @@ +package fr.maxlego08.menu.hooks.packetevents; + +public class PacketEventLoader { +} diff --git a/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/PacketListener.java b/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/PacketListener.java index f095373b..cc9a0cef 100644 --- a/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/PacketListener.java +++ b/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/PacketListener.java @@ -3,6 +3,7 @@ import com.github.retrooper.packetevents.event.PacketSendEvent; import com.github.retrooper.packetevents.protocol.item.ItemStack; import com.github.retrooper.packetevents.protocol.packettype.PacketType; +import com.github.retrooper.packetevents.protocol.packettype.PacketTypeCommon; import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerSetSlot; import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerWindowItems; import io.github.retrooper.packetevents.util.SpigotConversionUtil; @@ -15,7 +16,8 @@ public class PacketListener implements com.github.retrooper.packetevents.event.P @Override public void onPacketSend(PacketSendEvent event) { - if (event.getPacketType() == PacketType.Play.Server.WINDOW_ITEMS) { + PacketTypeCommon packetType = event.getPacketType(); + if (packetType == PacketType.Play.Server.WINDOW_ITEMS) { WrapperPlayServerWindowItems wrapper = new WrapperPlayServerWindowItems(event); if (PacketUtils.fakeContents.containsKey(event.getUser().getUUID())) { Player player = event.getPlayer(); @@ -33,7 +35,7 @@ public void onPacketSend(PacketSendEvent event) { wrapper.setItems(items); } - } else if (event.getPacketType() == PacketType.Play.Server.SET_SLOT) { + } else if (packetType == PacketType.Play.Server.SET_SLOT) { WrapperPlayServerSetSlot wrapper = new WrapperPlayServerSetSlot(event); if (PacketUtils.fakeContents.containsKey(event.getUser().getUUID())) { int slot = wrapper.getSlot(); diff --git a/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/PacketUtils.java b/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/PacketUtils.java index 458e04f3..f928e044 100644 --- a/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/PacketUtils.java +++ b/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/PacketUtils.java @@ -1,6 +1,7 @@ package fr.maxlego08.menu.hooks.packetevents; import com.github.retrooper.packetevents.PacketEvents; +import com.github.retrooper.packetevents.event.EventManager; import com.github.retrooper.packetevents.event.PacketListenerPriority; import fr.maxlego08.menu.api.Inventory; import fr.maxlego08.menu.api.InventoryListener; @@ -9,6 +10,7 @@ import fr.maxlego08.menu.api.engine.InventoryEngine; import fr.maxlego08.menu.api.engine.ItemButton; import fr.maxlego08.menu.api.utils.CompatibilityUtil; +import fr.maxlego08.menu.hooks.packetevents.listener.PacketAnimationListener; import fr.maxlego08.menu.zcore.logger.Logger; import io.github.retrooper.packetevents.factory.spigot.SpigotPacketEventsBuilder; import org.bukkit.entity.Player; @@ -35,7 +37,9 @@ public void onLoad() { public void onEnable() { PacketEvents.getAPI().init(); - PacketEvents.getAPI().getEventManager().registerListener(new PacketListener(), PacketListenerPriority.LOW); + EventManager eventManager = PacketEvents.getAPI().getEventManager(); +// eventManager.registerListener(new PacketListener(), PacketListenerPriority.LOW); + eventManager.registerListener(new PacketAnimationListener(this.plugin), PacketListenerPriority.LOW); } public void onDisable() { diff --git a/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/animation/PacketPlayerTitleAnimation.java b/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/animation/PacketPlayerTitleAnimation.java new file mode 100644 index 00000000..9f772678 --- /dev/null +++ b/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/animation/PacketPlayerTitleAnimation.java @@ -0,0 +1,45 @@ +package fr.maxlego08.menu.hooks.packetevents.animation; + +import com.github.retrooper.packetevents.PacketEvents; +import com.github.retrooper.packetevents.manager.player.PlayerManager; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerOpenWindow; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerWindowItems; +import fr.maxlego08.menu.api.MenuPlugin; +import fr.maxlego08.menu.api.animation.PlayerTitleAnimation; +import fr.maxlego08.menu.api.animation.TitleAnimationSettings; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.NonNull; + +import java.util.List; + +public class PacketPlayerTitleAnimation extends PlayerTitleAnimation { + private final WrapperPlayServerOpenWindow wrapperPlayServerOpenWindow; + private final PlayerManager playerManager = PacketEvents.getAPI().getPlayerManager(); + + private WrapperPlayServerWindowItems wrapperPlayServerWindowItems; + + public PacketPlayerTitleAnimation(MenuPlugin plugin, TitleAnimationSettings settings, int containerId, InventoryType type, int size, Object... args) { + super(plugin, settings, containerId, type, size); + this.wrapperPlayServerOpenWindow = (WrapperPlayServerOpenWindow) args[0]; + } + + public void setWrapperPlayServerWindowItems(WrapperPlayServerWindowItems wrapperPlayServerWindowItems) { + this.wrapperPlayServerWindowItems = wrapperPlayServerWindowItems; + } + + @Override + public void sendTitle(@NonNull Player player, @NonNull String title) { + this.wrapperPlayServerOpenWindow.setTitle(this.metaUpdater.getComponent(title)); + this.playerManager.sendPacket(player, this.wrapperPlayServerOpenWindow); + } + + @Override + public void sendInventoryContent(@NotNull Player player, @NotNull List inventoryContents) { + if (this.wrapperPlayServerWindowItems != null) { + this.playerManager.sendPacket(player, this.wrapperPlayServerWindowItems); + } + } +} \ No newline at end of file diff --git a/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/animation/PacketTitleAnimation.java b/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/animation/PacketTitleAnimation.java new file mode 100644 index 00000000..796598bd --- /dev/null +++ b/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/animation/PacketTitleAnimation.java @@ -0,0 +1,20 @@ +package fr.maxlego08.menu.hooks.packetevents.animation; + +import fr.maxlego08.menu.api.MenuPlugin; +import fr.maxlego08.menu.api.animation.PlayerTitleAnimation; +import fr.maxlego08.menu.api.animation.TitleAnimation; +import fr.maxlego08.menu.api.animation.TitleAnimationSettings; +import org.bukkit.event.inventory.InventoryType; + +public class PacketTitleAnimation implements TitleAnimation { + private final TitleAnimationSettings settings; + + public PacketTitleAnimation(TitleAnimationSettings settings) { + this.settings = settings; + } + + @Override + public PlayerTitleAnimation playTitleAnimation(MenuPlugin plugin, int containerId, InventoryType type, int size, Object... args) { + return new PacketPlayerTitleAnimation(plugin, this.settings, containerId, type, size, args); + } +} diff --git a/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/listener/PacketAnimationListener.java b/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/listener/PacketAnimationListener.java new file mode 100644 index 00000000..576347c1 --- /dev/null +++ b/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/listener/PacketAnimationListener.java @@ -0,0 +1,119 @@ +package fr.maxlego08.menu.hooks.packetevents.listener; + +import com.github.retrooper.packetevents.event.PacketListener; +import com.github.retrooper.packetevents.event.PacketSendEvent; +import com.github.retrooper.packetevents.protocol.packettype.PacketType; +import com.github.retrooper.packetevents.protocol.packettype.PacketTypeCommon; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerOpenWindow; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerWindowItems; +import fr.maxlego08.menu.api.MenuPlugin; +import fr.maxlego08.menu.api.animation.PlayerTitleAnimation; +import fr.maxlego08.menu.api.animation.TitleAnimation; +import fr.maxlego08.menu.api.engine.BaseInventory; +import fr.maxlego08.menu.api.utils.CompatibilityUtil; +import fr.maxlego08.menu.hooks.packetevents.animation.PacketPlayerTitleAnimation; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class PacketAnimationListener implements PacketListener { + private final Map playerAnimationData = new HashMap<>(); + private final MenuPlugin plugin; + + public PacketAnimationListener(MenuPlugin plugin) { + this.plugin = plugin; + } + + private static class PlayerAnimationData { + private int containerId; + private int windowId; + + public PlayerAnimationData(int containerId) { + this.containerId = containerId; + } + + public int getContainerId() { + return this.containerId; + } + + public void setContainerId(int containerId) { + this.containerId = containerId; + } + + public int getWindowId() { + return this.windowId; + } + + public void setWindowId(int windowId) { + this.windowId = windowId; + } + } + + @Override + public void onPacketSend(PacketSendEvent event) { + PacketTypeCommon packetType = event.getPacketType(); + if (packetType == PacketType.Play.Server.OPEN_WINDOW){ + WrapperPlayServerOpenWindow wrapper = new WrapperPlayServerOpenWindow(event); + int containerId = wrapper.getContainerId(); + Player player = event.getPlayer(); + UUID playerUniqueId = player.getUniqueId(); + + PlayerAnimationData data = this.playerAnimationData.get(playerUniqueId); + if (data != null && data.getContainerId() == containerId){ + return; + } + + InventoryHolder holder = CompatibilityUtil.getTopInventory(player).getHolder(); + if (holder instanceof BaseInventory baseInventory){ + TitleAnimation titleAnimation = baseInventory.getTitleAnimation(); + if (titleAnimation == null) { + return; + } + Inventory inventory = baseInventory.getInventory(); + PlayerTitleAnimation playerTitleAnimation = titleAnimation.playTitleAnimation(this.plugin, containerId, inventory.getType(), inventory.getSize(), wrapper); + if (playerTitleAnimation != null){ + playerTitleAnimation.start(player, Arrays.asList(inventory.getContents())); + } + baseInventory.setPlayerTitleAnimation(playerTitleAnimation); + this.playerAnimationData.put(playerUniqueId, new PlayerAnimationData(containerId)); + } + } else if (packetType == PacketType.Play.Server.CLOSE_WINDOW){ + Player player = event.getPlayer(); + InventoryHolder holder = CompatibilityUtil.getTopInventory(player).getHolder(); + if (holder instanceof BaseInventory){ + UUID playerUniqueId = player.getUniqueId(); + this.playerAnimationData.remove(playerUniqueId); + } + } else if (packetType == PacketType.Play.Server.WINDOW_ITEMS){ + WrapperPlayServerWindowItems wrapper = new WrapperPlayServerWindowItems(event); + Player player = event.getPlayer(); + UUID playerUniqueId = player.getUniqueId(); + + int windowId = wrapper.getWindowId(); + + PlayerAnimationData data = this.playerAnimationData.get(playerUniqueId); + if (data != null && data.getWindowId() == windowId){ + return; + } + + if (data == null) { + data = new PlayerAnimationData(0); + this.playerAnimationData.put(playerUniqueId, data); + } + data.setWindowId(windowId); + + InventoryHolder holder = CompatibilityUtil.getTopInventory(player).getHolder(); + if (holder instanceof BaseInventory baseInventory && baseInventory.getPlayerTitleAnimation() instanceof PacketPlayerTitleAnimation playerTitleAnimation){ + playerTitleAnimation.setWrapperPlayServerWindowItems(wrapper); + if (playerTitleAnimation.getSettings().showItemsAfterAnimation()){ + event.setCancelled(true); + } + } + } + } +} diff --git a/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/loader/PacketEventTitleAnimationLoader.java b/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/loader/PacketEventTitleAnimationLoader.java new file mode 100644 index 00000000..17c85bbf --- /dev/null +++ b/Hooks/PacketEvents/src/main/java/fr/maxlego08/menu/hooks/packetevents/loader/PacketEventTitleAnimationLoader.java @@ -0,0 +1,16 @@ +package fr.maxlego08.menu.hooks.packetevents.loader; + +import fr.maxlego08.menu.api.animation.TitleAnimation; +import fr.maxlego08.menu.api.animation.TitleAnimationLoader; +import fr.maxlego08.menu.api.exceptions.InventoryException; +import fr.maxlego08.menu.hooks.packetevents.animation.PacketTitleAnimation; +import org.bukkit.configuration.file.YamlConfiguration; +import org.jetbrains.annotations.NotNull; + +public class PacketEventTitleAnimationLoader extends TitleAnimationLoader { + + @Override + public TitleAnimation load(@NotNull YamlConfiguration configuration, @NotNull String path, Object... objects) throws InventoryException { + return new PacketTitleAnimation(super.loadSettings(configuration, path)); + } +} diff --git a/Hooks/Paper/build.gradle.kts b/Hooks/Paper/build.gradle.kts index a9492a11..17df373c 100644 --- a/Hooks/Paper/build.gradle.kts +++ b/Hooks/Paper/build.gradle.kts @@ -7,5 +7,5 @@ repositories { dependencies { compileOnly(projects.api) compileOnly("net.kyori:adventure-text-minimessage:4.21.0") - compileOnly("io.papermc.paper:paper-api:1.21.10-R0.1-SNAPSHOT") + compileOnly("io.papermc.paper:paper-api:1.21.11-R0.1-SNAPSHOT") } \ No newline at end of file diff --git a/Hooks/Paper/src/main/java/fr/maxlego08/menu/Test.java b/Hooks/Paper/src/main/java/fr/maxlego08/menu/Test.java new file mode 100644 index 00000000..2783406e --- /dev/null +++ b/Hooks/Paper/src/main/java/fr/maxlego08/menu/Test.java @@ -0,0 +1,10 @@ +package fr.maxlego08.menu; + +import org.bukkit.inventory.ItemStack; + +public class Test { + private void test(ItemStack item) { + +// item.setData(DataComponentTypes.BLOCK_DATA); + } +} diff --git a/Hooks/Paper/src/main/java/fr/maxlego08/menu/hooks/ComponentMeta.java b/Hooks/Paper/src/main/java/fr/maxlego08/menu/hooks/ComponentMeta.java index 2b437cad..bb5ea86e 100644 --- a/Hooks/Paper/src/main/java/fr/maxlego08/menu/hooks/ComponentMeta.java +++ b/Hooks/Paper/src/main/java/fr/maxlego08/menu/hooks/ComponentMeta.java @@ -2,7 +2,7 @@ import fr.maxlego08.menu.api.MenuPlugin; import fr.maxlego08.menu.api.utils.LoreType; -import fr.maxlego08.menu.api.utils.MetaUpdater; +import fr.maxlego08.menu.api.utils.PaperMetaUpdater; import fr.maxlego08.menu.api.utils.SimpleCache; import net.kyori.adventure.inventory.Book; import net.kyori.adventure.text.Component; @@ -33,7 +33,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -public class ComponentMeta implements MetaUpdater { +public class ComponentMeta implements PaperMetaUpdater { private static final Pattern LEGACY_HEX_PATTERN = Pattern.compile("§x(§[0-9a-fA-F]){6}"); private static final Pattern HEX_SHORT_PATTERN = Pattern.compile("(?") || text.contains("") || text.contains("") ? TextDecoration.State.TRUE : TextDecoration.State.FALSE; } - public Component getComponent(String text) { - return this.MINI_MESSAGE.deserialize(colorMiniMessage(text)); + @Override + public @NonNull Component getComponent(String text) { + return this.cache.get(text, ()->this.MINI_MESSAGE.deserialize(colorMiniMessage(text))); } private void updateDisplayName(ItemMeta itemMeta, String text) { diff --git a/Hooks/build.gradle.kts b/Hooks/build.gradle.kts index f6104a85..6db3de73 100644 --- a/Hooks/build.gradle.kts +++ b/Hooks/build.gradle.kts @@ -1,6 +1,8 @@ group = "Hooks" dependencies { + api(projects.common) + rootProject.subprojects.filter { it.path.startsWith(":Hooks:") }.forEach { subproject -> api(project(subproject.path)) } diff --git a/build.gradle.kts b/build.gradle.kts index c5c20fe7..b612efae 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -87,7 +87,7 @@ allprojects { dependencies { if (project.name != "Paper") { - compileOnly("org.spigotmc:spigot-api:1.21.10-R0.1-SNAPSHOT") + compileOnly("org.spigotmc:spigot-api:1.21.11-R0.1-SNAPSHOT") } compileOnly("com.mojang:authlib:1.5.26") compileOnly("me.clip:placeholderapi:2.11.6") @@ -107,6 +107,7 @@ repositories { dependencies { api(projects.api) + api(projects.common) api(projects.hooks) implementation("de.tr7zw:item-nbt-api:2.15.0") } diff --git a/instruction.md b/instruction.md new file mode 100644 index 00000000..4049f970 --- /dev/null +++ b/instruction.md @@ -0,0 +1,48 @@ +# Annotation Guidelines for API Module + +## Overview +When editing or adding classes in the `API/` folder, it is mandatory to use nullability and contract annotations on all function signatures. This ensures code safety and communicates intended behavior to integrators and tools. + +## Checklist for Annotation Usage + +1. **Required Annotations** + - Use `@NotNull` or `@Nullable` to indicate the nullability of return values and parameters. + - Use JetBrains `@Contract` to describe method purity and return contracts (e.g., `pure = true`, `"_ -> this"`, etc.) where applicable. + +2. **How to Annotate** + - Place annotations directly above the function signature. + - If the function already has a Javadoc or block comment, insert annotations between the comment and the function declaration. + +3. **Interfaces & Implementations** + - For functions defined in interfaces: + - Review all implementing classes. + - Choose nullability (NotNull/Nullable) based on the behavior of all implementors. + - If implementations vary in nullability, default to `@Nullable` or refactor for consistency. + +4. **Annotation Imports** + - Use JetBrains annotations (`org.jetbrains.annotations.*`) for `@Nullable`, `@NotNull`, and `@Contract`. + - If the project or consumer codebase uses a different annotation package, maintain consistency. + +5. **Documentation** + - If the function has a comment, do not remove or overwrite it. + - Place annotations after the comment and before the function definition. + +## Example + +```java +/** + * Returns the current user session. + * @return the current session or null if none exists + */ +@Nullable +public Session getCurrentSession(); + +/** + * Performs a reset operation. + */ +@NotNull +@Contract(pure = true, value = "-> this") +MyApi reset(); +``` + +--- diff --git a/opencode.json b/opencode.json new file mode 100644 index 00000000..a7164965 --- /dev/null +++ b/opencode.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://opencode.ai/config.json", + "theme": "tokyonight", + "disabled_providers": ["anthropic", "openai", "google", "aws-bedrock", "azure", "ollama", "openrouter", "gemini", "vertex", "openai-compatible", "opencode"], + "share": "disabled", + "agent": { + "build": { + "mode": "primary" + } + }, + "keybinds": { + "input_paste": "ctrl+v" + } +} \ No newline at end of file diff --git a/src/main/java/fr/maxlego08/menu/ZInventory.java b/src/main/java/fr/maxlego08/menu/ZInventory.java index c31216d6..400bc84b 100644 --- a/src/main/java/fr/maxlego08/menu/ZInventory.java +++ b/src/main/java/fr/maxlego08/menu/ZInventory.java @@ -2,6 +2,7 @@ import fr.maxlego08.menu.api.Inventory; import fr.maxlego08.menu.api.MenuItemStack; +import fr.maxlego08.menu.api.animation.TitleAnimation; import fr.maxlego08.menu.api.button.Button; import fr.maxlego08.menu.api.button.PaginateButton; import fr.maxlego08.menu.api.engine.InventoryEngine; @@ -44,6 +45,7 @@ public class ZInventory extends ZUtils implements Inventory { private OpenWithItem openWithItem; private InventoryType type = InventoryType.CHEST; private String targetPlayerNamePlaceholder; + private TitleAnimation titleAnimation; /** * @param plugin The plugin where the inventory comes from @@ -359,6 +361,16 @@ public String getTargetPlayerNamePlaceholder() { return targetPlayerNamePlaceholder; } + @Override + public void setTitleAnimation(TitleAnimation load) { + this.titleAnimation = load; + } + + @Override + public TitleAnimation getTitleAnimation() { + return this.titleAnimation; + } + public void setTargetPlayerNamePlaceholder(String targetPlaceholder) { this.targetPlayerNamePlaceholder = targetPlaceholder; } diff --git a/src/main/java/fr/maxlego08/menu/ZMenuPlugin.java b/src/main/java/fr/maxlego08/menu/ZMenuPlugin.java index 51297da6..226e0eeb 100644 --- a/src/main/java/fr/maxlego08/menu/ZMenuPlugin.java +++ b/src/main/java/fr/maxlego08/menu/ZMenuPlugin.java @@ -36,6 +36,8 @@ import fr.maxlego08.menu.hooks.itemsadder.ItemsAdderLoader; import fr.maxlego08.menu.hooks.mythicmobs.MythicManager; import fr.maxlego08.menu.hooks.mythicmobs.MythicMobsItemsLoader; +import fr.maxlego08.menu.hooks.packetevents.PacketUtils; +import fr.maxlego08.menu.hooks.packetevents.loader.PacketEventTitleAnimationLoader; import fr.maxlego08.menu.inventory.VInventoryManager; import fr.maxlego08.menu.inventory.inventories.InventoryDefault; import fr.maxlego08.menu.listener.AdapterListener; @@ -92,6 +94,7 @@ public class ZMenuPlugin extends ZPlugin implements MenuPlugin { private final StorageManager storageManager = new ZStorageManager(this); private final ButtonManager buttonManager = new ZButtonManager(this); private final InventoryManager inventoryManager = new ZInventoryManager(this); + private final TitleAnimationManager titleAnimationManager = new ZTitleAnimationManager(); private final CommandManager commandManager = new ZCommandManager(this); private final MessageLoader messageLoader = new MessageLoader(this); private final DataManager dataManager = new ZDataManager(this); @@ -111,7 +114,7 @@ public class ZMenuPlugin extends ZPlugin implements MenuPlugin { private MetaUpdater metaUpdater = new ClassicMeta(); private FoliaLib foliaLib; private AttributApplier attributApplier = new ApplySpigotAttribute(); - // private final PacketUtils packetUtils = new PacketUtils(this); + private final PacketUtils packetUtils = new PacketUtils(this); public static ZMenuPlugin getInstance() { return instance; @@ -119,14 +122,14 @@ public static ZMenuPlugin getInstance() { @Override public void onLoad() { - // this.packetUtils.onLoad(); + this.packetUtils.onLoad(); } @Override public void onEnable() { instance = this; - // this.packetUtils.onEnable(); + this.packetUtils.onEnable(); this.scheduler = (this.foliaLib = new FoliaLib(this)).getScheduler(); @@ -170,6 +173,7 @@ public void onEnable() { servicesManager.register(PatternManager.class, this.patternManager, this, ServicePriority.Highest); servicesManager.register(DupeManager.class, this.dupeManager, this, ServicePriority.Highest); servicesManager.register(Enchantments.class, this.enchantments, this, ServicePriority.Highest); + servicesManager.register(TitleAnimationManager.class, this.titleAnimationManager, this, ServicePriority.Highest); if (isPaper() && NmsVersion.getCurrentVersion().isDialogsVersion()){ if (Configuration.enableMiniMessageFormat){ @@ -244,7 +248,7 @@ public void onEnable() { this.websiteManager.loadPlaceholders(); this.dataManager.loadDefaultValues(); - // this.inventoryManager.registerInventoryListener(this.packetUtils); +// this.inventoryManager.registerInventoryListener(this.packetUtils); this.postEnable(); } @@ -342,6 +346,9 @@ private void registerHooks() { if (this.isActive(Plugins.BREWERYX)) { this.inventoryManager.registerMaterialLoader(new BreweryXLoader()); } + if (this.isActive(Plugins.PACKETEVENTS)){ + this.titleAnimationManager.registerLoader("packet-events", new PacketEventTitleAnimationLoader()); + } } @@ -389,7 +396,9 @@ public void onDisable() { Token.getInstance().save(this.getPersist()); } this.itemManager.unloadListeners(); - // this.packetUtils.onDisable(); + this.packetUtils.onDisable(); + + getServer().getServicesManager().unregisterAll(this); this.postDisable(); } @@ -450,6 +459,11 @@ public AttributApplier getAttributApplier() { return this.attributApplier; } + @Override + public TitleAnimationManager getTitleAnimationManager() { + return this.titleAnimationManager; + } + @Override public StorageManager getStorageManager() { return this.storageManager; diff --git a/src/main/java/fr/maxlego08/menu/ZTitleAnimationManager.java b/src/main/java/fr/maxlego08/menu/ZTitleAnimationManager.java new file mode 100644 index 00000000..d958eb9f --- /dev/null +++ b/src/main/java/fr/maxlego08/menu/ZTitleAnimationManager.java @@ -0,0 +1,35 @@ +package fr.maxlego08.menu; + +import fr.maxlego08.menu.api.TitleAnimationManager; +import fr.maxlego08.menu.api.animation.TitleAnimationLoader; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class ZTitleAnimationManager implements TitleAnimationManager { + private final Map loaders = new HashMap<>(); + + public ZTitleAnimationManager() { + } + + @Override + public boolean registerLoader(@NotNull String id, @NotNull TitleAnimationLoader loader) { + if (this.loaders.containsKey(id)) { + return false; + } + this.loaders.put(id, loader); + return true; + } + + @Override + public Optional getLoader(@NotNull String id) { + return Optional.ofNullable(this.loaders.get(id)); + } + + @Override + public Optional getFirstLoader() { + return this.loaders.values().stream().findFirst(); + } +} diff --git a/src/main/java/fr/maxlego08/menu/inventory/VInventory.java b/src/main/java/fr/maxlego08/menu/inventory/VInventory.java index 968a0244..0b6468d6 100644 --- a/src/main/java/fr/maxlego08/menu/inventory/VInventory.java +++ b/src/main/java/fr/maxlego08/menu/inventory/VInventory.java @@ -2,6 +2,8 @@ import fr.maxlego08.menu.ZMenuPlugin; import fr.maxlego08.menu.api.InventoryListener; +import fr.maxlego08.menu.api.animation.PlayerTitleAnimation; +import fr.maxlego08.menu.api.animation.TitleAnimation; import fr.maxlego08.menu.api.configuration.Configuration; import fr.maxlego08.menu.api.engine.BaseInventory; import fr.maxlego08.menu.api.engine.InventoryResult; @@ -35,6 +37,8 @@ public abstract class VInventory extends ZUtils implements Cloneable, BaseInvent protected String guiName; protected boolean disableClick = true; protected boolean disablePlayerInventoryClick = true; + private TitleAnimation titleAnimation; + private PlayerTitleAnimation playerTitleAnimation; private boolean isClose = false; @@ -206,6 +210,9 @@ protected InventoryResult preOpenInventory(ZMenuPlugin main, Player player, int protected void onPreClose(InventoryCloseEvent event, ZMenuPlugin plugin, Player player) { this.isClose = true; + if (this.playerTitleAnimation != null){ + this.playerTitleAnimation.stop(); + } this.onClose(event, plugin, player); } @@ -249,6 +256,26 @@ public void setDisablePlayerInventoryClick(boolean disablePlayerInventoryClick) this.disablePlayerInventoryClick = disablePlayerInventoryClick; } + @Override + public void setPlayerTitleAnimation(PlayerTitleAnimation playerTitleAnimation){ + this.playerTitleAnimation = playerTitleAnimation; + } + + @Override + public PlayerTitleAnimation getPlayerTitleAnimation(){ + return this.playerTitleAnimation; + } + + @Override + public void setTitleAnimation(TitleAnimation animation){ + this.titleAnimation = animation; + } + + @Override + public TitleAnimation getTitleAnimation(){ + return this.titleAnimation; + } + public void onInventoryClick(InventoryClickEvent event, ZMenuPlugin plugin, Player player) { } diff --git a/src/main/java/fr/maxlego08/menu/inventory/inventories/InventoryDefault.java b/src/main/java/fr/maxlego08/menu/inventory/inventories/InventoryDefault.java index db0edd23..2898c6de 100644 --- a/src/main/java/fr/maxlego08/menu/inventory/inventories/InventoryDefault.java +++ b/src/main/java/fr/maxlego08/menu/inventory/inventories/InventoryDefault.java @@ -92,6 +92,7 @@ public InventoryResult openInventory(ZMenuPlugin main, Player player, int page, } else { super.createMetaInventory(super.papi(placeholders.parse(inventoryName), targetPlayer, false), this.inventory.getType()); } + super.setTitleAnimation(this.inventory.getTitleAnimation()); // Display fill items if (this.inventory.getFillItemStack() != null) { ItemStack builtItem = this.inventory.getFillItemStack().build(player); diff --git a/src/main/java/fr/maxlego08/menu/loader/InventoryLoader.java b/src/main/java/fr/maxlego08/menu/loader/InventoryLoader.java index 176dd63e..fb67e342 100644 --- a/src/main/java/fr/maxlego08/menu/loader/InventoryLoader.java +++ b/src/main/java/fr/maxlego08/menu/loader/InventoryLoader.java @@ -5,6 +5,9 @@ import fr.maxlego08.menu.api.Inventory; import fr.maxlego08.menu.api.InventoryOption; import fr.maxlego08.menu.api.MenuItemStack; +import fr.maxlego08.menu.api.TitleAnimationManager; +import fr.maxlego08.menu.api.animation.TitleAnimation; +import fr.maxlego08.menu.api.animation.TitleAnimationLoader; import fr.maxlego08.menu.api.button.Button; import fr.maxlego08.menu.api.configuration.Configuration; import fr.maxlego08.menu.api.exceptions.InventoryException; @@ -117,6 +120,23 @@ public Inventory load(@NonNull YamlConfiguration configuration, @NonNull String inventory = new ZInventory(this.plugin, name, fileName, size, buttons); } + if (configuration.isConfigurationSection("title-animation")){ + String titleAnimationPath = "title-animation."; + String pluginName = configuration.getString(titleAnimationPath + "plugin"); + Optional titleAnimationLoader; + TitleAnimationManager titleAnimationManager = this.plugin.getTitleAnimationManager(); + if (pluginName == null){ + titleAnimationLoader = titleAnimationManager.getFirstLoader(); + } else { + titleAnimationLoader = titleAnimationManager.getLoader(pluginName); + } + if (titleAnimationLoader.isPresent()) { + TitleAnimationLoader animationLoader = titleAnimationLoader.get(); + TitleAnimation titleAnimation = animationLoader.load(configuration, titleAnimationPath, file); + inventory.setTitleAnimation(titleAnimation); + } + } + inventory.setType(inventoryType); inventory.setUpdateInterval(configuration.getInt(path + "update-interval", configuration.getInt(path + "updateInterval", 1000))); inventory.setClearInventory(configuration.getBoolean(path + "clear-inventory", configuration.getBoolean(path + "clearInventory", false))); diff --git a/src/main/java/fr/maxlego08/menu/zcore/utils/plugins/Plugins.java b/src/main/java/fr/maxlego08/menu/zcore/utils/plugins/Plugins.java index 3c07b830..c4d79f35 100644 --- a/src/main/java/fr/maxlego08/menu/zcore/utils/plugins/Plugins.java +++ b/src/main/java/fr/maxlego08/menu/zcore/utils/plugins/Plugins.java @@ -1,5 +1,11 @@ package fr.maxlego08.menu.zcore.utils.plugins; +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + public enum Plugins { VAULT("Vault"), @@ -27,9 +33,10 @@ public enum Plugins { NEXTGENS("NextGens"), MYTHICMOBS("MythicMobs"), ZMENUPLUS("zMenuPlus"), - BREWERYX("BreweryX") + BREWERYX("BreweryX"), + PACKETEVENTS("packetevents") ; - + private static final Map presenceCache = new ConcurrentHashMap<>(); private final String name; Plugins(String name) { @@ -43,4 +50,14 @@ public String getName() { return name; } + public boolean isPresent() { + return presenceCache.computeIfAbsent(this, plugin -> { + return Bukkit.getServer().getPluginManager().getPlugin(name) != null; + }); + } + public boolean isEnabled() { + Plugin bukkitPlugin = Bukkit.getServer().getPluginManager().getPlugin(name); + return bukkitPlugin != null && bukkitPlugin.isEnabled(); + } + } From a45bdbcbaaa2f9fc65cc66dbe6f212adaa58c34a Mon Sep 17 00:00:00 2001 From: 1robie Date: Wed, 14 Jan 2026 20:01:28 +0100 Subject: [PATCH 02/56] feat: add item component system with BlockStateComponent and loader --- .../fr/maxlego08/menu/ComponentsManager.java | 14 +++++++ .../fr/maxlego08/menu/api/MenuItemStack.java | 6 +++ .../fr/maxlego08/menu/api/MenuPlugin.java | 3 ++ .../menu/api/itemstack/ItemComponent.java | 12 ++++++ .../menu/api/loader/ItemComponentLoader.java | 23 ++++++++++ .../fr/maxlego08/menu/api/utils/ItemUtil.java | 22 ++++++++++ .../src/main/java/fr/maxlego08/menu/Test.java | 10 ----- .../fr/maxlego08/menu/ZComponentsManager.java | 31 ++++++++++++++ .../fr/maxlego08/menu/ZMenuItemStack.java | 19 +++++++++ .../java/fr/maxlego08/menu/ZMenuPlugin.java | 24 ++++++++--- .../components/BlockStateComponent.java | 42 +++++++++++++++++++ .../menu/loader/MenuItemStackLoader.java | 19 +++++++++ .../BlockStateItemComponentLoader.java | 32 ++++++++++++++ .../menu/zcore/utils/plugins/Plugins.java | 18 -------- 14 files changed, 242 insertions(+), 33 deletions(-) create mode 100644 API/src/main/java/fr/maxlego08/menu/ComponentsManager.java create mode 100644 API/src/main/java/fr/maxlego08/menu/api/itemstack/ItemComponent.java create mode 100644 API/src/main/java/fr/maxlego08/menu/api/loader/ItemComponentLoader.java create mode 100644 API/src/main/java/fr/maxlego08/menu/api/utils/ItemUtil.java delete mode 100644 Hooks/Paper/src/main/java/fr/maxlego08/menu/Test.java create mode 100644 src/main/java/fr/maxlego08/menu/ZComponentsManager.java create mode 100644 src/main/java/fr/maxlego08/menu/itemstack/components/BlockStateComponent.java create mode 100644 src/main/java/fr/maxlego08/menu/loader/components/BlockStateItemComponentLoader.java diff --git a/API/src/main/java/fr/maxlego08/menu/ComponentsManager.java b/API/src/main/java/fr/maxlego08/menu/ComponentsManager.java new file mode 100644 index 00000000..53aea414 --- /dev/null +++ b/API/src/main/java/fr/maxlego08/menu/ComponentsManager.java @@ -0,0 +1,14 @@ +package fr.maxlego08.menu; + +import fr.maxlego08.menu.api.loader.ItemComponentLoader; +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; + +public interface ComponentsManager { + + void registerComponent(@NotNull ItemComponentLoader loader); + + @NotNull + Optional getLoader(@NotNull String key) throws IllegalArgumentException; +} diff --git a/API/src/main/java/fr/maxlego08/menu/api/MenuItemStack.java b/API/src/main/java/fr/maxlego08/menu/api/MenuItemStack.java index 3d1d1669..be834de4 100644 --- a/API/src/main/java/fr/maxlego08/menu/api/MenuItemStack.java +++ b/API/src/main/java/fr/maxlego08/menu/api/MenuItemStack.java @@ -11,7 +11,9 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import java.util.Collection; import java.util.List; import java.util.Map; @@ -707,4 +709,8 @@ public interface MenuItemStack { * @param clearDefaultAttributes {@code true} to clear default attributes. */ void setClearDefaultAttributes(boolean clearDefaultAttributes); + + Collection<@NotNull ItemComponent> getItemComponents(); + + void addItemComponent(@NotNull ItemComponent itemMetadata); } diff --git a/API/src/main/java/fr/maxlego08/menu/api/MenuPlugin.java b/API/src/main/java/fr/maxlego08/menu/api/MenuPlugin.java index f19c4270..b05ecd2f 100644 --- a/API/src/main/java/fr/maxlego08/menu/api/MenuPlugin.java +++ b/API/src/main/java/fr/maxlego08/menu/api/MenuPlugin.java @@ -1,6 +1,7 @@ package fr.maxlego08.menu.api; import com.tcoded.folialib.impl.PlatformScheduler; +import fr.maxlego08.menu.ComponentsManager; import fr.maxlego08.menu.api.attribute.AttributApplier; import fr.maxlego08.menu.api.command.CommandManager; import fr.maxlego08.menu.api.dupe.DupeManager; @@ -253,4 +254,6 @@ public interface MenuPlugin extends Plugin { AttributApplier getAttributApplier(); TitleAnimationManager getTitleAnimationManager(); + + ComponentsManager getComponentsManager(); } diff --git a/API/src/main/java/fr/maxlego08/menu/api/itemstack/ItemComponent.java b/API/src/main/java/fr/maxlego08/menu/api/itemstack/ItemComponent.java new file mode 100644 index 00000000..db334734 --- /dev/null +++ b/API/src/main/java/fr/maxlego08/menu/api/itemstack/ItemComponent.java @@ -0,0 +1,12 @@ +package fr.maxlego08.menu.api.itemstack; + +import org.bukkit.OfflinePlayer; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface ItemComponent { + + void apply(@NotNull ItemStack itemStack, @Nullable OfflinePlayer player); + +} diff --git a/API/src/main/java/fr/maxlego08/menu/api/loader/ItemComponentLoader.java b/API/src/main/java/fr/maxlego08/menu/api/loader/ItemComponentLoader.java new file mode 100644 index 00000000..76360ff5 --- /dev/null +++ b/API/src/main/java/fr/maxlego08/menu/api/loader/ItemComponentLoader.java @@ -0,0 +1,23 @@ +package fr.maxlego08.menu.api.loader; + +import fr.maxlego08.menu.api.itemstack.ItemComponent; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public abstract class ItemComponentLoader { + private final String componentName; + + public ItemComponentLoader(@NotNull String componentName) { + this.componentName = componentName; + } + + @Nullable + public abstract ItemComponent load(@NotNull YamlConfiguration configuration, @NotNull String path, @NotNull ConfigurationSection componentSection); + + @NotNull + public String getComponentName() { + return this.componentName; + } +} diff --git a/API/src/main/java/fr/maxlego08/menu/api/utils/ItemUtil.java b/API/src/main/java/fr/maxlego08/menu/api/utils/ItemUtil.java new file mode 100644 index 00000000..86681189 --- /dev/null +++ b/API/src/main/java/fr/maxlego08/menu/api/utils/ItemUtil.java @@ -0,0 +1,22 @@ +package fr.maxlego08.menu.api.utils; + +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.function.Consumer; + +public class ItemUtil { + public static boolean editMeta(ItemStack item, Class metaClass, Consumer consumer) { + ItemMeta meta = item.getItemMeta(); + if (meta == null) { + return false; + } + if (metaClass.isInstance(meta)) { + T metaItem = metaClass.cast(meta); + consumer.accept(metaItem); + item.setItemMeta(metaItem); + return true; + } + return false; + } +} diff --git a/Hooks/Paper/src/main/java/fr/maxlego08/menu/Test.java b/Hooks/Paper/src/main/java/fr/maxlego08/menu/Test.java deleted file mode 100644 index 2783406e..00000000 --- a/Hooks/Paper/src/main/java/fr/maxlego08/menu/Test.java +++ /dev/null @@ -1,10 +0,0 @@ -package fr.maxlego08.menu; - -import org.bukkit.inventory.ItemStack; - -public class Test { - private void test(ItemStack item) { - -// item.setData(DataComponentTypes.BLOCK_DATA); - } -} diff --git a/src/main/java/fr/maxlego08/menu/ZComponentsManager.java b/src/main/java/fr/maxlego08/menu/ZComponentsManager.java new file mode 100644 index 00000000..a90b2116 --- /dev/null +++ b/src/main/java/fr/maxlego08/menu/ZComponentsManager.java @@ -0,0 +1,31 @@ +package fr.maxlego08.menu; + +import fr.maxlego08.menu.api.loader.ItemComponentLoader; +import fr.maxlego08.menu.loader.components.BlockStateItemComponentLoader; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class ZComponentsManager implements ComponentsManager { + private final Map components = new HashMap<>(); + + ZComponentsManager(){ + this.registerComponent(new BlockStateItemComponentLoader()); + } + + @Override + public void registerComponent(@NotNull ItemComponentLoader loader) { + String componentName = loader.getComponentName(); + if (this.components.containsKey(componentName)) { + throw new IllegalArgumentException("Component with name " + componentName + " is already registered."); + } + this.components.put(componentName, loader); + } + + @Override + public @NotNull Optional getLoader(@NotNull String key) { + return Optional.ofNullable(this.components.get(key)); + } +} diff --git a/src/main/java/fr/maxlego08/menu/ZMenuItemStack.java b/src/main/java/fr/maxlego08/menu/ZMenuItemStack.java index e61bfe39..83ad26ec 100644 --- a/src/main/java/fr/maxlego08/menu/ZMenuItemStack.java +++ b/src/main/java/fr/maxlego08/menu/ZMenuItemStack.java @@ -32,6 +32,7 @@ import org.bukkit.inventory.meta.*; import org.bukkit.inventory.meta.trim.ArmorTrim; import org.bukkit.potion.PotionType; +import org.jetbrains.annotations.NotNull; import org.jspecify.annotations.NonNull; import javax.annotation.Nullable; @@ -89,6 +90,8 @@ public class ZMenuItemStack extends ZUtils implements MenuItemStack { private MenuItemRarity itemRarity; private TrimConfiguration trimConfiguration; + private final List itemComponents = new ArrayList<>(); + public ZMenuItemStack(InventoryManager inventoryManager, String filePath, String path) { this.inventoryManager = inventoryManager; this.filePath = filePath; @@ -148,6 +151,12 @@ public static ZMenuItemStack fromMap(InventoryManager inventoryManager, File fil applyItemMeta(player, placeholders, offlinePlayer, useCache, itemStack); applyAttributes(itemStack); + if (this.itemComponents != null && !this.itemComponents.isEmpty()) { + for (ItemComponent metadata : this.itemComponents) { + metadata.apply(itemStack, offlinePlayer); + } + } + if (!needPlaceholderAPI &&Configuration.enableCacheItemStack) { this.cacheItemStack = itemStack; } @@ -1144,4 +1153,14 @@ public boolean isClearDefaultAttributes() { public void setClearDefaultAttributes(boolean clearDefaultAttributes) { this.clearDefaultAttributes = clearDefaultAttributes; } + + @Override + public Collection<@NotNull ItemComponent> getItemComponents() { + return Collections.unmodifiableCollection(this.itemComponents); + } + + @Override + public void addItemComponent(@NotNull ItemComponent itemMetadata) { + this.itemComponents.add(itemMetadata); + } } diff --git a/src/main/java/fr/maxlego08/menu/ZMenuPlugin.java b/src/main/java/fr/maxlego08/menu/ZMenuPlugin.java index 226e0eeb..980ce113 100644 --- a/src/main/java/fr/maxlego08/menu/ZMenuPlugin.java +++ b/src/main/java/fr/maxlego08/menu/ZMenuPlugin.java @@ -103,8 +103,10 @@ public class ZMenuPlugin extends ZPlugin implements MenuPlugin { private final PatternManager patternManager = new ZPatternManager(this); private final Enchantments enchantments = new ZEnchantments(); private final ItemManager itemManager = new ZItemManager(this); + private final ComponentsManager componentsManager = new ZComponentsManager(); private final Map globalPlaceholders = new HashMap<>(); private final ToastHelper toastHelper = new ToastManager(this); + private final AttributApplier attributApplier = new ApplySpigotAttribute(); private final File configFile = new File(getDataFolder(), "config.yml"); private DialogManager dialogManager; private CommandMenu commandMenu; @@ -113,8 +115,7 @@ public class ZMenuPlugin extends ZPlugin implements MenuPlugin { private FontImage fontImage = new EmptyFont(); private MetaUpdater metaUpdater = new ClassicMeta(); private FoliaLib foliaLib; - private AttributApplier attributApplier = new ApplySpigotAttribute(); - private final PacketUtils packetUtils = new PacketUtils(this); + private PacketUtils packetUtils; public static ZMenuPlugin getInstance() { return instance; @@ -122,14 +123,19 @@ public static ZMenuPlugin getInstance() { @Override public void onLoad() { - this.packetUtils.onLoad(); + if (this.isActive(Plugins.PACKETEVENTS)) { + this.packetUtils = new PacketUtils(this); + this.packetUtils.onLoad(); + } } @Override public void onEnable() { instance = this; - this.packetUtils.onEnable(); + + if (this.packetUtils != null) + this.packetUtils.onEnable(); this.scheduler = (this.foliaLib = new FoliaLib(this)).getScheduler(); @@ -395,8 +401,11 @@ public void onDisable() { if (Token.token != null) { Token.getInstance().save(this.getPersist()); } + this.itemManager.unloadListeners(); - this.packetUtils.onDisable(); + + if (this.packetUtils != null) + this.packetUtils.onDisable(); getServer().getServicesManager().unregisterAll(this); @@ -464,6 +473,11 @@ public TitleAnimationManager getTitleAnimationManager() { return this.titleAnimationManager; } + @Override + public ComponentsManager getComponentsManager() { + return this.componentsManager; + } + @Override public StorageManager getStorageManager() { return this.storageManager; diff --git a/src/main/java/fr/maxlego08/menu/itemstack/components/BlockStateComponent.java b/src/main/java/fr/maxlego08/menu/itemstack/components/BlockStateComponent.java new file mode 100644 index 00000000..476b4aa0 --- /dev/null +++ b/src/main/java/fr/maxlego08/menu/itemstack/components/BlockStateComponent.java @@ -0,0 +1,42 @@ +package fr.maxlego08.menu.itemstack.components; + +import fr.maxlego08.menu.api.itemstack.ItemComponent; +import fr.maxlego08.menu.api.utils.ItemUtil; +import fr.maxlego08.menu.zcore.logger.Logger; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.block.data.BlockData; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.BlockDataMeta; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public record BlockStateComponent(String blockState) implements ItemComponent { + @Override + public void apply(@NotNull ItemStack itemStack, @Nullable OfflinePlayer player) { + try { + BlockData blockData = Bukkit.createBlockData(itemStack.getType(), getFinalBlockState(this.blockState)); + boolean apply = ItemUtil.editMeta(itemStack, BlockDataMeta.class, meta -> { + meta.setBlockData(blockData); + }); + if (!apply){ + Logger.info("Failed to apply BlockData to ItemStack of type "+itemStack.getType().name()); + } + } catch (IllegalArgumentException e) { + Logger.info("Invalid block state '" + this.blockState + "' for item type " + itemStack.getType().name()); + } + } + + @NotNull + private String getFinalBlockState(@NotNull String blockState){ + StringBuilder finalState = new StringBuilder(); + if (!blockState.startsWith("[")){ + finalState.append("["); + } + finalState.append(blockState); + if (!blockState.endsWith("]")) { + finalState.append("]"); + } + return finalState.toString(); + } +} diff --git a/src/main/java/fr/maxlego08/menu/loader/MenuItemStackLoader.java b/src/main/java/fr/maxlego08/menu/loader/MenuItemStackLoader.java index a0152983..116b88dd 100644 --- a/src/main/java/fr/maxlego08/menu/loader/MenuItemStackLoader.java +++ b/src/main/java/fr/maxlego08/menu/loader/MenuItemStackLoader.java @@ -1,5 +1,6 @@ package fr.maxlego08.menu.loader; +import fr.maxlego08.menu.ComponentsManager; import fr.maxlego08.menu.ZMenuItemStack; import fr.maxlego08.menu.api.InventoryManager; import fr.maxlego08.menu.api.MenuItemStack; @@ -10,6 +11,7 @@ import fr.maxlego08.menu.api.enums.MenuItemRarity; import fr.maxlego08.menu.api.exceptions.ItemEnchantException; import fr.maxlego08.menu.api.itemstack.*; +import fr.maxlego08.menu.api.loader.ItemComponentLoader; import fr.maxlego08.menu.api.utils.Loader; import fr.maxlego08.menu.api.utils.LoreType; import fr.maxlego08.menu.api.utils.TrimHelper; @@ -96,6 +98,23 @@ public MenuItemStack load(@NonNull YamlConfiguration configuration, @NonNull Str if (NmsVersion.getCurrentVersion().isNewItemModelAPI()) { menuItemStack.setItemModel(configuration.getString(path + "item-model")); } + + ConfigurationSection componentsSection = configuration.getConfigurationSection(path + "components."); + if (componentsSection != null) { + ComponentsManager componentsManager = this.manager.getPlugin().getComponentsManager(); + for (String componentKey : componentsSection.getKeys(false)){ + ConfigurationSection componentSection = componentsSection.getConfigurationSection(componentKey); + if (componentSection == null) continue; + Optional optionalItemComponentLoader = componentsManager.getLoader(componentKey); + if (optionalItemComponentLoader.isPresent()){ + ItemComponent itemComponent = optionalItemComponentLoader.get().load(configuration, path + "components." + componentKey + ".", componentSection); + if (itemComponent != null){ + menuItemStack.addItemComponent(itemComponent); + } + } + } + } + return menuItemStack; } diff --git a/src/main/java/fr/maxlego08/menu/loader/components/BlockStateItemComponentLoader.java b/src/main/java/fr/maxlego08/menu/loader/components/BlockStateItemComponentLoader.java new file mode 100644 index 00000000..dc0a1b0b --- /dev/null +++ b/src/main/java/fr/maxlego08/menu/loader/components/BlockStateItemComponentLoader.java @@ -0,0 +1,32 @@ +package fr.maxlego08.menu.loader.components; + +import fr.maxlego08.menu.api.itemstack.ItemComponent; +import fr.maxlego08.menu.api.loader.ItemComponentLoader; +import fr.maxlego08.menu.itemstack.components.BlockStateComponent; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +public class BlockStateItemComponentLoader extends ItemComponentLoader { + + public BlockStateItemComponentLoader(){ + super("block_state"); + } + + @Override + public @Nullable ItemComponent load(@NotNull YamlConfiguration configuration, @NotNull String path, @NotNull ConfigurationSection componentSection) { + Map blockStates = componentSection.getValues(true); + if (blockStates.isEmpty()) return null; + String blockStatesString = blockStates.toString(); + StringBuilder blockStateBuilder = new StringBuilder(); + if (blockStatesString.startsWith("{") && blockStatesString.endsWith("}")) { + blockStateBuilder.append(blockStatesString, 1, blockStatesString.length() - 1); + } else { + blockStateBuilder.append(blockStatesString); + } + return new BlockStateComponent(blockStateBuilder.toString()); + } +} diff --git a/src/main/java/fr/maxlego08/menu/zcore/utils/plugins/Plugins.java b/src/main/java/fr/maxlego08/menu/zcore/utils/plugins/Plugins.java index c4d79f35..5f7d3853 100644 --- a/src/main/java/fr/maxlego08/menu/zcore/utils/plugins/Plugins.java +++ b/src/main/java/fr/maxlego08/menu/zcore/utils/plugins/Plugins.java @@ -1,11 +1,5 @@ package fr.maxlego08.menu.zcore.utils.plugins; -import org.bukkit.Bukkit; -import org.bukkit.plugin.Plugin; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - public enum Plugins { VAULT("Vault"), @@ -36,7 +30,6 @@ public enum Plugins { BREWERYX("BreweryX"), PACKETEVENTS("packetevents") ; - private static final Map presenceCache = new ConcurrentHashMap<>(); private final String name; Plugins(String name) { @@ -49,15 +42,4 @@ public enum Plugins { public String getName() { return name; } - - public boolean isPresent() { - return presenceCache.computeIfAbsent(this, plugin -> { - return Bukkit.getServer().getPluginManager().getPlugin(name) != null; - }); - } - public boolean isEnabled() { - Plugin bukkitPlugin = Bukkit.getServer().getPluginManager().getPlugin(name); - return bukkitPlugin != null && bukkitPlugin.isEnabled(); - } - } From 8a3e0e66c6df5cfc129713fdd4c9fe5a2e125d51 Mon Sep 17 00:00:00 2001 From: 1robie Date: Wed, 14 Jan 2026 20:28:17 +0100 Subject: [PATCH 03/56] feat: enhance ItemComponentLoader with owner type and plugin support --- .../menu/api/loader/ItemComponentLoader.java | 67 +++++++++++++++++++ .../fr/maxlego08/menu/ZComponentsManager.java | 11 +-- 2 files changed, 74 insertions(+), 4 deletions(-) diff --git a/API/src/main/java/fr/maxlego08/menu/api/loader/ItemComponentLoader.java b/API/src/main/java/fr/maxlego08/menu/api/loader/ItemComponentLoader.java index 76360ff5..dd093c4e 100644 --- a/API/src/main/java/fr/maxlego08/menu/api/loader/ItemComponentLoader.java +++ b/API/src/main/java/fr/maxlego08/menu/api/loader/ItemComponentLoader.java @@ -3,21 +3,88 @@ import fr.maxlego08.menu.api.itemstack.ItemComponent; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.plugin.Plugin; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@SuppressWarnings("unused") public abstract class ItemComponentLoader { + public enum ComponentOwnerType { + MINECRAFT, + PLUGIN + } + private final String componentName; + private final ComponentOwnerType ownerType; + @Nullable + private final Plugin plugin; public ItemComponentLoader(@NotNull String componentName) { + this(componentName, ComponentOwnerType.MINECRAFT, null); + } + + /** + * Constructor for plugin-owned components. + * @param componentName The name of the component + * @param plugin The plugin that owns this component + */ + public ItemComponentLoader(@NotNull String componentName, @NotNull Plugin plugin) { + this(componentName, ComponentOwnerType.PLUGIN, plugin); + } + + /** + * General constructor for ItemComponentLoader. + * @param componentName The name of the component + * @param ownerType The owner type of the component + * @param plugin The plugin that owns this component, can be null if ownerType is MINECRAFT + * @throws IllegalArgumentException if ownerType is PLUGIN and plugin is null + */ + public ItemComponentLoader(@NotNull String componentName, @NotNull ComponentOwnerType ownerType, @Nullable Plugin plugin) throws IllegalArgumentException { + if (ownerType == ComponentOwnerType.PLUGIN && plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null for PLUGIN owned components."); + } this.componentName = componentName; + this.ownerType = ownerType; + this.plugin = plugin; } @Nullable public abstract ItemComponent load(@NotNull YamlConfiguration configuration, @NotNull String path, @NotNull ConfigurationSection componentSection); + /** + * Get all possible names for this component, including namespaced versions. + * E.g., for Minecraft components, both "name" and "minecraft:name" are returned. + * For plugin components, "pluginname:name" is returned. + * @return An unmodifiable list of component names + */ + @NotNull + public List getComponentNames() { + List names = new ArrayList<>(); + if (this.ownerType == ComponentOwnerType.MINECRAFT) { + names.add(this.componentName); + names.add("minecraft:" + this.componentName); + } else if (this.ownerType == ComponentOwnerType.PLUGIN && this.plugin != null) { + names.add(this.plugin.getName().toLowerCase() + ":" + this.componentName); + } + return Collections.unmodifiableList(names); + } + @NotNull public String getComponentName() { return this.componentName; } + + @NotNull + public ComponentOwnerType getOwnerType() { + return this.ownerType; + } + + @Nullable + public Plugin getPlugin() { + return this.plugin; + } } diff --git a/src/main/java/fr/maxlego08/menu/ZComponentsManager.java b/src/main/java/fr/maxlego08/menu/ZComponentsManager.java index a90b2116..8f47415c 100644 --- a/src/main/java/fr/maxlego08/menu/ZComponentsManager.java +++ b/src/main/java/fr/maxlego08/menu/ZComponentsManager.java @@ -5,6 +5,7 @@ import org.jetbrains.annotations.NotNull; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; @@ -17,11 +18,13 @@ public class ZComponentsManager implements ComponentsManager { @Override public void registerComponent(@NotNull ItemComponentLoader loader) { - String componentName = loader.getComponentName(); - if (this.components.containsKey(componentName)) { - throw new IllegalArgumentException("Component with name " + componentName + " is already registered."); + List componentNames = loader.getComponentNames(); + for (String name : componentNames) { + if (this.components.containsKey(name)) { + throw new IllegalArgumentException("Component with name '" + name + "' is already registered."); + } + this.components.put(name, loader); } - this.components.put(componentName, loader); } @Override From c7f78db9b63ad62d377515009aded4cb1f60bbed Mon Sep 17 00:00:00 2001 From: 1robie Date: Wed, 14 Jan 2026 21:14:09 +0100 Subject: [PATCH 04/56] feat: introduce new item component system with support for Minecraft 1.20.5+ and enhanced loader methods --- AGENTS.md | 131 ------------------ .../fr/maxlego08/menu/ComponentsManager.java | 15 +- changelog.md | 3 + .../fr/maxlego08/menu/ZComponentsManager.java | 4 +- .../menu/loader/MenuItemStackLoader.java | 24 ++-- .../menu/zcore/utils/nms/NmsVersion.java | 26 +++- 6 files changed, 54 insertions(+), 149 deletions(-) delete mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 141bcd37..00000000 --- a/AGENTS.md +++ /dev/null @@ -1,131 +0,0 @@ -# AGENTS.md - -> **Agentic Automation & Development Guide** -> zMenu / GroupeZ-dev - ---- - -**Quick Reference Links:** -- [Documentation](https://docs.zmenu.dev/) -- [API Javadocs](https://javadocs.groupez.dev/zmenu) -- [Discord Support](https://discord.groupez.dev/) -- [README](./README.md) -- [Changelog](./changelog.md) - ---- - -## 📦 Build, Lint, and Test Commands - -| Task | Gradle Command | Notes | -|------------------------|--------------------------------------|---------------------------------------------------| -| Build all | `./gradlew build` | Output: `target/`, API jars: `target-api/` | -| Lint (CI) | _Super-Linter in GitHub Actions_ | On PRs/push to `main`, `develop` | -| Test all | `./gradlew test` | JUnit 5, discovers test files by convention | -| Test single class | `./gradlew test --tests '*ClassName'`| Use fully qualified names as needed | -| Test single method | `./gradlew test --tests '*ClassName.methodName'` | Regexp/wildcard match supported | -| Full clean | `./gradlew clean build` | Removes build artifacts first | - -**CI/Automation Lint:** -- Linting runs via GitHub Actions: `super-linter`. -- Enforced on all pushes/PRs to protected branches. -- Locally, linting and formatting must match `.editorconfig` and IntelliJ IDE. - -**Dependency/Gradle Guidance:** -- Strictly define all dependencies/plugins in `build.gradle.kts`. **Never** edit lock files by hand. -- Default toolchain: **Java 21** (see `toolchain` block). -- When adding dependencies, follow indentation and style of the root config. -- Output is produced in `target/` and `target-api/` folders. - -**Troubleshooting:** -- Ensure you use the provided `gradlew`/`gradlew.bat` scripts, **not** system Gradle. -- Line-length, indentation, or import order are the most common causes of build/lint failures. - -**Test Discovery:** -- JUnit test classes should be placed in the standard `src/test/java` directories within each module, named with `*Test.java` or `*Tests.java` by convention. - ---- - -## 🎨 Code Style, Formatting, and Project Conventions - -> Agents must strictly auto-correct all code style violations before merge or patch submission. - -### General Formatting: -- **Spaces only** (`indent_style = space`), **4 spaces for Java**, **2 for YAML** (`indent_size`). -- **Line length:** max **160** chars (`max_line_length`). -- **Encoding:** UTF-8. End-of-line: unset/IDE default. No required final newline. -- **Always** use 4-space indentation for Java (see `.editorconfig` `[*.java]`), 2-space for YAML. -- **Blank lines:** Keep two between top-level elements, and one after imports/package statements. -- **IntelliJ auto-format** must be respected (`ij_*` settings in `.editorconfig` control detailed wrapping, alignment, etc). - -### Imports: -- IntelliJ style: All wildcard imports (`@*`), static imports (`*`), then `javax.**`, `java.**`, then user packages (`$*`), with one blank line between groups. -- Do **not** use import wildcards unless >5 imports from the same package. -- Place imports after `package` declarations, before class definition. -- No imports for inner classes unless specifically required. - -### Java Indentation, Wrapping, & Structure (from `.editorconfig`): -- Braces style: **end_of_line** (Egyptian braces: `{` on same line) -- Wrapping: Most methods, params, and arrays: do **not** wrap by default. Builder/annotation/params: wrap before elements, not after. -- Case/switch: indent by one level, case on separate line. -- Import/order/blank lines: See `ij_java_*` rules for precise handling. -- YAML files: indent with 2 spaces (see `[*.yml,*.yaml]`). - -### Javadoc & Comments: -- Use `/** ... */` for Javadoc on **all public classes/methods**. -- Align params, throws, and returns vertically. Always add a blank after the description. -- Block/line comments: Space after `//`, align at column start. - -### Naming Conventions: -- Classes: PascalCase. Methods/vars: camelCase. Constants: UPPER_SNAKE_CASE. Interfaces: end with -able/-Listener or semantic (`FooListener`), else PascalCase. -- Packages: all lowercase, dot-separated. Resources/configs: kebab-case, lower-case extensions. - -### Spacing & Operator Rules: -- Always put spaces after commas/semicolons; before `{`; around `=`, `+`, etc. -- **No** space between method name and parenthesis: `foo(bar)`, not `foo (bar)`. -- No extra spaces inside parentheses. - -### Other Style Essentials: -- Arrays/lists: no newline after `{`, one entry per line unless >80 chars. -- “Widget/annotation/parameter” lists: split lines before each element if multiline. -- Do not enforce a final newline in code or config files. -- Use `.editorconfig` and reformat via IDE or script before reviews. - ---- - -## 📋 Error Handling & Logging -- **Always** check for null unless contractually guaranteed. -- Prefer granular `try/catch` blocks—provide **full tracebacks** for errors. -- **Never** `System.out.println`—use `fr.maxlego08.menu.zcore.logger.Logger` error/info methods. -- Log **all** exceptions at error level, with useful context, not just stack. -- For all command/argument validation, employ validation helpers and user-facing feedback. - ---- - -## 🛠 Project Architecture Guidelines -- Multi-module: key folders are `API/`, `Common/`, and `Hooks/`. -- Shared logic goes in `Common`; API contracts and interfaces in `API`; integrations in `Hooks`. -- **No cross-module imports** except by well-defined APIs. -- Plugins/libraries must follow `build.gradle.kts` group/versioning style. -- **Java 21** required (Gradle enforced). -- Test code: keep *only* in designated test source roots. - ---- - -## 🕵️‍♂️ Agent Protocols & Good Practices -- Run `./gradlew build` and `./gradlew test` prior to PR or merge. -- Run **specific test** before pushing: `./gradlew test --tests '*ClassName.methodName'`. -- If touching dependencies, **update version** and add a changelog entry. -- Reference official docs/readme/JavaDocs for context! See top links. -- **Respect modular boundaries:** Do not blur API/adapters/core code. -- **Commit messages:** clear and intent-driven (e.g. `fix: ...`, `feat: ...`, `refactor: ...`). -- **Prior to merge:** Ensure auto-format ({IDE} or script) matches `.editorconfig`. -- **If no AGENTS.md:** You must maintain and update this file after material changes! - -## For More Info -- [Documentation](https://docs.zmenu.dev/) -- [API Javadocs](https://javadocs.groupez.dev/zmenu) -- [Discord (support)](https://discord.groupez.dev/) - ---- - -**ALL AGENTS** must enforce these rules before checking in code, running PRs, or post-processing patches. Lint, test, and *never* introduce manual code style drift. Reference and update this file regularly! \ No newline at end of file diff --git a/API/src/main/java/fr/maxlego08/menu/ComponentsManager.java b/API/src/main/java/fr/maxlego08/menu/ComponentsManager.java index 53aea414..faea2f1d 100644 --- a/API/src/main/java/fr/maxlego08/menu/ComponentsManager.java +++ b/API/src/main/java/fr/maxlego08/menu/ComponentsManager.java @@ -6,9 +6,18 @@ import java.util.Optional; public interface ComponentsManager { + /** + * Register a new ItemComponentLoader. + * @param loader The loader to register + * @throws IllegalArgumentException if a loader with the same name is already registered + **/ + void registerComponent(@NotNull ItemComponentLoader loader) throws IllegalArgumentException; - void registerComponent(@NotNull ItemComponentLoader loader); - + /** + * Get an ItemComponentLoader by its name. + * @param name The name of the loader + * @return An Optional containing the loader if found, or empty if not found + **/ @NotNull - Optional getLoader(@NotNull String key) throws IllegalArgumentException; + Optional getLoader(@NotNull String name); } diff --git a/changelog.md b/changelog.md index a0140a33..7c76fdf2 100644 --- a/changelog.md +++ b/changelog.md @@ -42,6 +42,9 @@ # Unreleased +- New component item system for 1.20.5+ +- Animated title (Required `PacketEvent`) +- Added automatic support for newer Minecraft versions when no major API changes are present - Fix Avast flag issue on VirusTotal - Introduced the actions_patterns system, which lets you define default actions applied to all buttons unless they already specify those action types. - MiniMessage support in toasts. diff --git a/src/main/java/fr/maxlego08/menu/ZComponentsManager.java b/src/main/java/fr/maxlego08/menu/ZComponentsManager.java index 8f47415c..a9a9fda3 100644 --- a/src/main/java/fr/maxlego08/menu/ZComponentsManager.java +++ b/src/main/java/fr/maxlego08/menu/ZComponentsManager.java @@ -28,7 +28,7 @@ public void registerComponent(@NotNull ItemComponentLoader loader) { } @Override - public @NotNull Optional getLoader(@NotNull String key) { - return Optional.ofNullable(this.components.get(key)); + public @NotNull Optional getLoader(@NotNull String name) { + return Optional.ofNullable(this.components.get(name)); } } diff --git a/src/main/java/fr/maxlego08/menu/loader/MenuItemStackLoader.java b/src/main/java/fr/maxlego08/menu/loader/MenuItemStackLoader.java index 116b88dd..df806799 100644 --- a/src/main/java/fr/maxlego08/menu/loader/MenuItemStackLoader.java +++ b/src/main/java/fr/maxlego08/menu/loader/MenuItemStackLoader.java @@ -99,17 +99,19 @@ public MenuItemStack load(@NonNull YamlConfiguration configuration, @NonNull Str menuItemStack.setItemModel(configuration.getString(path + "item-model")); } - ConfigurationSection componentsSection = configuration.getConfigurationSection(path + "components."); - if (componentsSection != null) { - ComponentsManager componentsManager = this.manager.getPlugin().getComponentsManager(); - for (String componentKey : componentsSection.getKeys(false)){ - ConfigurationSection componentSection = componentsSection.getConfigurationSection(componentKey); - if (componentSection == null) continue; - Optional optionalItemComponentLoader = componentsManager.getLoader(componentKey); - if (optionalItemComponentLoader.isPresent()){ - ItemComponent itemComponent = optionalItemComponentLoader.get().load(configuration, path + "components." + componentKey + ".", componentSection); - if (itemComponent != null){ - menuItemStack.addItemComponent(itemComponent); + if (NmsVersion.getCurrentVersion().isAttributItemStack()) { // 1.20.5+ + ConfigurationSection componentsSection = configuration.getConfigurationSection(path + "components."); + if (componentsSection != null) { + ComponentsManager componentsManager = this.manager.getPlugin().getComponentsManager(); + for (String componentKey : componentsSection.getKeys(false)) { + ConfigurationSection componentSection = componentsSection.getConfigurationSection(componentKey); + if (componentSection == null) continue; + Optional optionalItemComponentLoader = componentsManager.getLoader(componentKey); + if (optionalItemComponentLoader.isPresent()) { + ItemComponent itemComponent = optionalItemComponentLoader.get().load(configuration, path + "components." + componentKey + ".", componentSection); + if (itemComponent != null) { + menuItemStack.addItemComponent(itemComponent); + } } } } diff --git a/src/main/java/fr/maxlego08/menu/zcore/utils/nms/NmsVersion.java b/src/main/java/fr/maxlego08/menu/zcore/utils/nms/NmsVersion.java index d024ff22..84db6abf 100644 --- a/src/main/java/fr/maxlego08/menu/zcore/utils/nms/NmsVersion.java +++ b/src/main/java/fr/maxlego08/menu/zcore/utils/nms/NmsVersion.java @@ -1,5 +1,6 @@ package fr.maxlego08.menu.zcore.utils.nms; +import fr.maxlego08.menu.zcore.logger.Logger; import org.bukkit.Bukkit; import java.util.regex.Matcher; @@ -56,7 +57,10 @@ public enum NmsVersion { V_1_21_7(1217), V_1_21_8(1218), V_1_21_9(1219), - V_1_21_10(1210) + V_1_21_10(12110), + V_1_21_11(12111), + + UNKNOWN(Integer.MAX_VALUE) ; @@ -80,10 +84,28 @@ private static NmsVersion getNmsVersion(){ Matcher matcher = Pattern.compile("(?\\d+\\.\\d+)(?\\.\\d+)?").matcher(Bukkit.getBukkitVersion()); int currentVersion = matcher.find() ? Integer.parseInt(matcher.group("version").replace(".", "") + (matcher.group("patch") != null ? matcher.group("patch").replace(".", "") : "0")) : 0; - // Returns the version closest to the current version + NmsVersion highestSupportedVersionEnum = V_1_8_8; + for (NmsVersion value : values()) { + if (value != UNKNOWN && value.version > highestSupportedVersionEnum.version) { + highestSupportedVersionEnum = value; + } + } + + if (currentVersion > highestSupportedVersionEnum.version) { + Logger.info(String.format( + "Running Minecraft %s (newer than highest supported version %s). " + + "Please report this version to help us add support. " + + "Check for plugin updates if you experience issues.", + currentVersion, + highestSupportedVersionEnum.name() + ), Logger.LogType.WARNING); + return UNKNOWN; + } + NmsVersion closest = V_1_12_2; int smallestDifference = Integer.MAX_VALUE; for (NmsVersion value : values()) { + if (value == UNKNOWN) continue; int difference = Math.abs(value.version - currentVersion); if (difference < smallestDifference) { smallestDifference = difference; From ff3a6e4f69e6236ffc61c7fa0d4960151f12b6d7 Mon Sep 17 00:00:00 2001 From: 1robie Date: Wed, 14 Jan 2026 21:15:19 +0100 Subject: [PATCH 05/56] delete: instruction.md --- instruction.md | 48 ------------------------------------------------ 1 file changed, 48 deletions(-) delete mode 100644 instruction.md diff --git a/instruction.md b/instruction.md deleted file mode 100644 index 4049f970..00000000 --- a/instruction.md +++ /dev/null @@ -1,48 +0,0 @@ -# Annotation Guidelines for API Module - -## Overview -When editing or adding classes in the `API/` folder, it is mandatory to use nullability and contract annotations on all function signatures. This ensures code safety and communicates intended behavior to integrators and tools. - -## Checklist for Annotation Usage - -1. **Required Annotations** - - Use `@NotNull` or `@Nullable` to indicate the nullability of return values and parameters. - - Use JetBrains `@Contract` to describe method purity and return contracts (e.g., `pure = true`, `"_ -> this"`, etc.) where applicable. - -2. **How to Annotate** - - Place annotations directly above the function signature. - - If the function already has a Javadoc or block comment, insert annotations between the comment and the function declaration. - -3. **Interfaces & Implementations** - - For functions defined in interfaces: - - Review all implementing classes. - - Choose nullability (NotNull/Nullable) based on the behavior of all implementors. - - If implementations vary in nullability, default to `@Nullable` or refactor for consistency. - -4. **Annotation Imports** - - Use JetBrains annotations (`org.jetbrains.annotations.*`) for `@Nullable`, `@NotNull`, and `@Contract`. - - If the project or consumer codebase uses a different annotation package, maintain consistency. - -5. **Documentation** - - If the function has a comment, do not remove or overwrite it. - - Place annotations after the comment and before the function definition. - -## Example - -```java -/** - * Returns the current user session. - * @return the current session or null if none exists - */ -@Nullable -public Session getCurrentSession(); - -/** - * Performs a reset operation. - */ -@NotNull -@Contract(pure = true, value = "-> this") -MyApi reset(); -``` - ---- From 373ffc2dff4ed5d1a998e7ef39f075ac7cab401e Mon Sep 17 00:00:00 2001 From: 1robie Date: Thu, 15 Jan 2026 18:39:25 +0100 Subject: [PATCH 06/56] feat: add various item component loaders and components for enhanced item functionality --- .../maxlego08/menu/api/InventoryManager.java | 3 + .../fr/maxlego08/menu/api/button/Button.java | 20 +- .../menu/api/engine/InventoryEngine.java | 23 ++ .../menu/api/itemstack/ItemComponent.java | 4 +- .../menu/api/loader/ItemComponentLoader.java | 10 +- .../fr/maxlego08/menu/api/utils/Tuples.java | 19 ++ .../fr/maxlego08/menu/ZComponentsManager.java | 33 ++- .../fr/maxlego08/menu/ZInventoryManager.java | 8 +- .../fr/maxlego08/menu/ZMenuItemStack.java | 4 +- .../java/fr/maxlego08/menu/ZMenuPlugin.java | 2 +- .../menu/button/buttons/ZInventoryButton.java | 2 +- .../inventories/InventoryDefault.java | 46 ++-- .../components/AttackRangeComponent.java | 31 +++ .../AttributeModifiersComponent.java | 27 +++ .../components/BannerPatternsComponent.java | 27 +++ .../components/BaseColorComponent.java | 25 +++ .../components/BlockStateComponent.java | 8 +- .../components/BlocksAttacksComponent.java | 52 +++++ .../components/BreakSoundComponent.java | 22 ++ .../components/BundleContentsComponent.java | 31 +++ .../ChargedProjectilesComponent.java | 31 +++ .../components/ConsumableComponent.java | 34 +++ .../components/ContainerComponent.java | 44 ++++ .../components/ContainerLootComponent.java | 32 +++ .../components/CustomDataComponent.java | 35 +++ .../menu/loader/MenuItemStackLoader.java | 3 +- .../AttackRangeItemComponentLoader.java | 71 +++++++ ...AttributeModifiersItemComponentLoader.java | 51 +++++ .../BannerPatternsItemComponentLoader.java | 38 ++++ .../BaseColorItemComponentLoader.java | 32 +++ .../BlockStateItemComponentLoader.java | 4 +- .../BlocksAttacksItemComponentLoader.java | 124 +++++++++++ .../BreakSoundItemComponentLoader.java | 37 ++++ .../BundleContentsItemComponentLoader.java | 30 +++ ...ChargedProjectilesItemComponentLoader.java | 29 +++ .../ConsumableItemComponentLoader.java | 199 ++++++++++++++++++ .../ContainerItemComponentLoader.java | 43 ++++ .../ContainerLootItemComponentLoader.java | 34 +++ .../CustomDataItemComponentLoader.java | 94 +++++++++ .../MenuItemStackListComponentLoaderBase.java | 49 +++++ .../requirement/actions/DialogAction.java | 6 +- .../requirement/actions/InventoryAction.java | 2 +- .../requirement/actions/RefreshAction.java | 4 +- .../menu/zcore/utils/InventoryArgument.java | 8 +- .../zcore/utils/itemstack/ContainerSlot.java | 7 + .../itemstack/DamageReductionRecord.java | 13 ++ .../itemstack/ZConsumableApplyEffects.java | 51 +++++ .../itemstack/ZConsumableClearEffects.java | 13 ++ .../utils/itemstack/ZConsumablePlaySound.java | 31 +++ .../itemstack/ZConsumableRemoveEffect.java | 39 ++++ .../ZConsumableTeleportRandomly.java | 29 +++ .../utils/itemstack/ZPersistentDataType.java | 8 + .../menu/zcore/utils/nms/NmsVersion.java | 12 ++ 53 files changed, 1590 insertions(+), 44 deletions(-) create mode 100644 API/src/main/java/fr/maxlego08/menu/api/utils/Tuples.java create mode 100644 src/main/java/fr/maxlego08/menu/itemstack/components/AttackRangeComponent.java create mode 100644 src/main/java/fr/maxlego08/menu/itemstack/components/AttributeModifiersComponent.java create mode 100644 src/main/java/fr/maxlego08/menu/itemstack/components/BannerPatternsComponent.java create mode 100644 src/main/java/fr/maxlego08/menu/itemstack/components/BaseColorComponent.java create mode 100644 src/main/java/fr/maxlego08/menu/itemstack/components/BlocksAttacksComponent.java create mode 100644 src/main/java/fr/maxlego08/menu/itemstack/components/BreakSoundComponent.java create mode 100644 src/main/java/fr/maxlego08/menu/itemstack/components/BundleContentsComponent.java create mode 100644 src/main/java/fr/maxlego08/menu/itemstack/components/ChargedProjectilesComponent.java create mode 100644 src/main/java/fr/maxlego08/menu/itemstack/components/ConsumableComponent.java create mode 100644 src/main/java/fr/maxlego08/menu/itemstack/components/ContainerComponent.java create mode 100644 src/main/java/fr/maxlego08/menu/itemstack/components/ContainerLootComponent.java create mode 100644 src/main/java/fr/maxlego08/menu/itemstack/components/CustomDataComponent.java create mode 100644 src/main/java/fr/maxlego08/menu/loader/components/AttackRangeItemComponentLoader.java create mode 100644 src/main/java/fr/maxlego08/menu/loader/components/AttributeModifiersItemComponentLoader.java create mode 100644 src/main/java/fr/maxlego08/menu/loader/components/BannerPatternsItemComponentLoader.java create mode 100644 src/main/java/fr/maxlego08/menu/loader/components/BaseColorItemComponentLoader.java create mode 100644 src/main/java/fr/maxlego08/menu/loader/components/BlocksAttacksItemComponentLoader.java create mode 100644 src/main/java/fr/maxlego08/menu/loader/components/BreakSoundItemComponentLoader.java create mode 100644 src/main/java/fr/maxlego08/menu/loader/components/BundleContentsItemComponentLoader.java create mode 100644 src/main/java/fr/maxlego08/menu/loader/components/ChargedProjectilesItemComponentLoader.java create mode 100644 src/main/java/fr/maxlego08/menu/loader/components/ConsumableItemComponentLoader.java create mode 100644 src/main/java/fr/maxlego08/menu/loader/components/ContainerItemComponentLoader.java create mode 100644 src/main/java/fr/maxlego08/menu/loader/components/ContainerLootItemComponentLoader.java create mode 100644 src/main/java/fr/maxlego08/menu/loader/components/CustomDataItemComponentLoader.java create mode 100644 src/main/java/fr/maxlego08/menu/loader/components/MenuItemStackListComponentLoaderBase.java create mode 100644 src/main/java/fr/maxlego08/menu/zcore/utils/itemstack/ContainerSlot.java create mode 100644 src/main/java/fr/maxlego08/menu/zcore/utils/itemstack/DamageReductionRecord.java create mode 100644 src/main/java/fr/maxlego08/menu/zcore/utils/itemstack/ZConsumableApplyEffects.java create mode 100644 src/main/java/fr/maxlego08/menu/zcore/utils/itemstack/ZConsumableClearEffects.java create mode 100644 src/main/java/fr/maxlego08/menu/zcore/utils/itemstack/ZConsumablePlaySound.java create mode 100644 src/main/java/fr/maxlego08/menu/zcore/utils/itemstack/ZConsumableRemoveEffect.java create mode 100644 src/main/java/fr/maxlego08/menu/zcore/utils/itemstack/ZConsumableTeleportRandomly.java create mode 100644 src/main/java/fr/maxlego08/menu/zcore/utils/itemstack/ZPersistentDataType.java diff --git a/API/src/main/java/fr/maxlego08/menu/api/InventoryManager.java b/API/src/main/java/fr/maxlego08/menu/api/InventoryManager.java index 045f7a8e..0b536a6d 100644 --- a/API/src/main/java/fr/maxlego08/menu/api/InventoryManager.java +++ b/API/src/main/java/fr/maxlego08/menu/api/InventoryManager.java @@ -14,6 +14,7 @@ import fr.maxlego08.menu.api.loader.MaterialLoader; import fr.maxlego08.menu.api.utils.Message; import fr.maxlego08.menu.api.utils.MetaUpdater; +import fr.maxlego08.menu.api.utils.Placeholders; import org.bukkit.OfflinePlayer; import org.bukkit.command.CommandSender; import org.bukkit.configuration.file.YamlConfiguration; @@ -559,6 +560,8 @@ public interface InventoryManager extends Listener { ItemStack postProcessSkullItemStack(ItemStack itemStack, Button button, Player player); + ItemStack postProcessSkullItemStack(ItemStack itemStack, Button button, Player player, Placeholders placeholders); + void sendMessage(CommandSender sender, Message message, Object... args); void load(); diff --git a/API/src/main/java/fr/maxlego08/menu/api/button/Button.java b/API/src/main/java/fr/maxlego08/menu/api/button/Button.java index 7a29a046..3e5b1cfc 100644 --- a/API/src/main/java/fr/maxlego08/menu/api/button/Button.java +++ b/API/src/main/java/fr/maxlego08/menu/api/button/Button.java @@ -75,13 +75,26 @@ public Button setItemStack(@Nullable MenuItemStack itemStack) { return this; } + @Deprecated @Contract(pure = true) @Nullable public ItemStack getCustomItemStack(@NotNull Player player) { + return this.getCustomItemStack(player, new Placeholders()); + } + + @Contract(pure = true) + @Nullable + public ItemStack getCustomItemStack(@NotNull Player player, @NotNull Placeholders placeholders) { + return this.getCustomItemStack(player, this.useCache, placeholders); + } + + @Contract(pure = true) + @Nullable + public ItemStack getCustomItemStack(@NotNull Player player, boolean useCache, @NotNull Placeholders placeholders) { if (this.itemStack == null) return null; - ItemStack itemStack = this.itemStack.build(player, this.useCache); + ItemStack itemStack = this.itemStack.build(player, useCache,placeholders); if (this.playerHead != null && itemStack.getItemMeta() instanceof SkullMeta) { - return this.plugin.getInventoryManager().postProcessSkullItemStack(itemStack, this, player); + return this.plugin.getInventoryManager().postProcessSkullItemStack(itemStack, this, player,placeholders); } return itemStack; } @@ -179,7 +192,7 @@ public void onRender(Player player, InventoryEngine inventoryEngine) { } slots[i] = slot; } - inventoryEngine.displayFinalButton(this, slots); + inventoryEngine.displayFinalButton(this, new Placeholders(), slots); } } @@ -542,7 +555,6 @@ public void setPlayerInventory(boolean inPlayerInventory) { * @param player the player to display the button for * @return the button to display */ - @Contract(pure = true, value = "_, _ -> this") public Button getDisplayButton(InventoryEngine inventoryEngine, Player player) { return this; } diff --git a/API/src/main/java/fr/maxlego08/menu/api/engine/InventoryEngine.java b/API/src/main/java/fr/maxlego08/menu/api/engine/InventoryEngine.java index 67743151..5149aa47 100644 --- a/API/src/main/java/fr/maxlego08/menu/api/engine/InventoryEngine.java +++ b/API/src/main/java/fr/maxlego08/menu/api/engine/InventoryEngine.java @@ -2,6 +2,7 @@ import fr.maxlego08.menu.api.Inventory; import fr.maxlego08.menu.api.button.Button; +import fr.maxlego08.menu.api.utils.Placeholders; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -18,12 +19,34 @@ public interface InventoryEngine extends BaseInventory { @NotNull List