From b639133fc93546a4aea8928676f3de9cc075e615 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Wed, 22 Apr 2026 14:36:58 +0500 Subject: [PATCH 01/71] chore: fix .DS_Store typo and ignore logs/+PR_DESCRIPTION.md Co-Authored-By: Claude Opus 4.7 (1M context) --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 6dea9cd..6c1a880 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ .gradle .idea build -.DS_Strore \ No newline at end of file +.DS_Store +logs +PR_DESCRIPTION.md From 7b43fa84f56dd008f633c8e175adf43b71a13114 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Wed, 22 Apr 2026 14:48:41 +0500 Subject: [PATCH 02/71] refactor: move plugin sources into plugin/ subproject (prep for multi-module) Co-Authored-By: Claude Opus 4.7 (1M context) --- build.gradle => plugin/build.gradle | 0 {libs => plugin/libs}/MMOItems-6.5.4.jar | Bin {libs => plugin/libs}/citizens-2.0.jar | Bin {libs => plugin/libs}/paper-1.20.6-147.jar | Bin proguard.txt => plugin/proguard.txt | 0 .../main/java/ru/abstractmenus/AbstractMenus.java | 0 .../src}/main/java/ru/abstractmenus/Constants.java | 0 .../src}/main/java/ru/abstractmenus/MainConfig.java | 0 .../src}/main/java/ru/abstractmenus/Metrics.java | 0 .../command/ArgumentParseException.java | 0 .../main/java/ru/abstractmenus/command/Command.java | 0 .../ru/abstractmenus/command/CommandContext.java | 0 .../ru/abstractmenus/command/CommandHandler.java | 0 .../ru/abstractmenus/command/CommandManager.java | 0 .../ru/abstractmenus/command/DefaultHandler.java | 0 .../ru/abstractmenus/command/args/Argument.java | 0 .../abstractmenus/command/args/ChoiceArgument.java | 0 .../abstractmenus/command/args/IntegerArgument.java | 0 .../abstractmenus/command/args/NumberArgument.java | 0 .../abstractmenus/command/args/PlayerArgument.java | 0 .../abstractmenus/command/args/StringArgument.java | 0 .../command/bukkit/CommandWrapper.java | 0 .../abstractmenus/commands/AbstractMenuCommand.java | 0 .../java/ru/abstractmenus/commands/Command.java | 0 .../java/ru/abstractmenus/commands/VarCommand.java | 0 .../java/ru/abstractmenus/commands/VarpCommand.java | 0 .../ru/abstractmenus/commands/am/CommandOpen.java | 0 .../commands/am/CommandPluginVersion.java | 0 .../ru/abstractmenus/commands/am/CommandReload.java | 0 .../ru/abstractmenus/commands/am/CommandServe.java | 0 .../java/ru/abstractmenus/commands/var/VarDec.java | 0 .../java/ru/abstractmenus/commands/var/VarDiv.java | 0 .../java/ru/abstractmenus/commands/var/VarGet.java | 0 .../java/ru/abstractmenus/commands/var/VarInc.java | 0 .../java/ru/abstractmenus/commands/var/VarMul.java | 0 .../java/ru/abstractmenus/commands/var/VarRem.java | 0 .../java/ru/abstractmenus/commands/var/VarSet.java | 0 .../ru/abstractmenus/commands/varp/VarpDec.java | 0 .../ru/abstractmenus/commands/varp/VarpDiv.java | 0 .../ru/abstractmenus/commands/varp/VarpGet.java | 0 .../ru/abstractmenus/commands/varp/VarpInc.java | 0 .../ru/abstractmenus/commands/varp/VarpMul.java | 0 .../ru/abstractmenus/commands/varp/VarpRem.java | 0 .../ru/abstractmenus/commands/varp/VarpSet.java | 0 .../main/java/ru/abstractmenus/data/Actions.java | 0 .../main/java/ru/abstractmenus/data/BannerData.java | 0 .../main/java/ru/abstractmenus/data/BookData.java | 0 .../main/java/ru/abstractmenus/data/EntityData.java | 0 .../abstractmenus/data/actions/ActionBookOpen.java | 0 .../abstractmenus/data/actions/ActionBroadcast.java | 0 .../data/actions/ActionBungeeConnect.java | 0 .../data/actions/ActionButtonRemove.java | 0 .../abstractmenus/data/actions/ActionButtonSet.java | 0 .../abstractmenus/data/actions/ActionCommand.java | 0 .../data/actions/ActionFoodLevelSet.java | 0 .../data/actions/ActionGameModeSet.java | 0 .../abstractmenus/data/actions/ActionGroupAdd.java | 0 .../data/actions/ActionGroupRemove.java | 0 .../abstractmenus/data/actions/ActionHealthSet.java | 0 .../abstractmenus/data/actions/ActionInputChat.java | 0 .../data/actions/ActionInventoryClear.java | 0 .../abstractmenus/data/actions/ActionItemAdd.java | 0 .../abstractmenus/data/actions/ActionItemClear.java | 0 .../data/actions/ActionItemRefresh.java | 0 .../data/actions/ActionItemRemove.java | 0 .../abstractmenus/data/actions/ActionLevelGive.java | 0 .../abstractmenus/data/actions/ActionLevelTake.java | 0 .../ru/abstractmenus/data/actions/ActionLog.java | 0 .../data/actions/ActionLuckPermsMetaRemove.java | 0 .../data/actions/ActionLuckPermsMetaSet.java | 0 .../abstractmenus/data/actions/ActionMenuClose.java | 0 .../abstractmenus/data/actions/ActionMenuOpen.java | 0 .../data/actions/ActionMenuOpenCtx.java | 0 .../data/actions/ActionMenuRefresh.java | 0 .../abstractmenus/data/actions/ActionMessage.java | 0 .../data/actions/ActionMiniMessage.java | 0 .../abstractmenus/data/actions/ActionMoneyGive.java | 0 .../abstractmenus/data/actions/ActionMoneyTake.java | 0 .../abstractmenus/data/actions/ActionPageNext.java | 0 .../abstractmenus/data/actions/ActionPagePrev.java | 0 .../data/actions/ActionPermissionGive.java | 0 .../data/actions/ActionPermissionRemove.java | 0 .../abstractmenus/data/actions/ActionPlaceItem.java | 0 .../data/actions/ActionPlacedItemRemove.java | 0 .../data/actions/ActionPlayerChat.java | 0 .../data/actions/ActionPotionEffect.java | 0 .../data/actions/ActionPotionEffectRemove.java | 0 .../data/actions/ActionPropertyRemove.java | 0 .../data/actions/ActionPropertySet.java | 0 .../abstractmenus/data/actions/ActionRecipeAdd.java | 0 .../abstractmenus/data/actions/ActionSkinReset.java | 0 .../abstractmenus/data/actions/ActionSkinSet.java | 0 .../ru/abstractmenus/data/actions/ActionSound.java | 0 .../data/actions/ActionSoundCustom.java | 0 .../abstractmenus/data/actions/ActionTeleport.java | 0 .../ru/abstractmenus/data/actions/ActionXpGive.java | 0 .../ru/abstractmenus/data/actions/ActionXpTake.java | 0 .../ru/abstractmenus/data/actions/MenuActions.java | 0 .../data/actions/var/ActionVarDec.java | 0 .../data/actions/var/ActionVarDiv.java | 0 .../data/actions/var/ActionVarInc.java | 0 .../data/actions/var/ActionVarMul.java | 0 .../data/actions/var/ActionVarRem.java | 0 .../data/actions/var/ActionVarSet.java | 0 .../data/actions/varp/ActionVarpDec.java | 0 .../data/actions/varp/ActionVarpDiv.java | 0 .../data/actions/varp/ActionVarpInc.java | 0 .../data/actions/varp/ActionVarpMul.java | 0 .../data/actions/varp/ActionVarpRem.java | 0 .../data/actions/varp/ActionVarpSet.java | 0 .../data/actions/wrappers/ActionBulk.java | 0 .../data/actions/wrappers/ActionDelay.java | 0 .../data/actions/wrappers/ActionPlayerScope.java | 0 .../data/actions/wrappers/ActionRandomActions.java | 0 .../data/activators/ActivatorUtil.java | 0 .../abstractmenus/data/activators/Activators.java | 0 .../abstractmenus/data/activators/OpenButton.java | 0 .../ru/abstractmenus/data/activators/OpenChat.java | 0 .../data/activators/OpenChatContains.java | 0 .../data/activators/OpenClickBlock.java | 0 .../data/activators/OpenClickBlockType.java | 0 .../data/activators/OpenClickEntity.java | 0 .../data/activators/OpenClickItem.java | 0 .../abstractmenus/data/activators/OpenClickNPC.java | 0 .../abstractmenus/data/activators/OpenCommand.java | 0 .../ru/abstractmenus/data/activators/OpenJoin.java | 0 .../ru/abstractmenus/data/activators/OpenLever.java | 0 .../ru/abstractmenus/data/activators/OpenPlate.java | 0 .../data/activators/OpenRegionEnter.java | 0 .../data/activators/OpenRegionLeave.java | 0 .../data/activators/OpenShiftClickEntity.java | 0 .../ru/abstractmenus/data/activators/OpenSign.java | 0 .../data/activators/OpenSwapItems.java | 0 .../ru/abstractmenus/data/catalogs/Catalogs.java | 0 .../abstractmenus/data/catalogs/EntityCatalog.java | 0 .../data/catalogs/IteratorCatalog.java | 0 .../abstractmenus/data/catalogs/PlayerCatalog.java | 0 .../abstractmenus/data/catalogs/ServerCatalog.java | 0 .../abstractmenus/data/catalogs/SliceCatalog.java | 0 .../abstractmenus/data/catalogs/WorldCatalog.java | 0 .../data/comparator/BooleanEvaluator.java | 0 .../data/comparator/LegacyValueComparator.java | 0 .../data/comparator/ModernValueComparator.java | 0 .../data/comparator/ValueComparator.java | 0 .../ru/abstractmenus/data/properties/ItemProps.java | 0 .../data/properties/PropAttributeModifier.java | 0 .../data/properties/PropBannerData.java | 0 .../abstractmenus/data/properties/PropBindings.java | 0 .../abstractmenus/data/properties/PropBookData.java | 0 .../ru/abstractmenus/data/properties/PropColor.java | 0 .../ru/abstractmenus/data/properties/PropCount.java | 0 .../abstractmenus/data/properties/PropDamage.java | 0 .../ru/abstractmenus/data/properties/PropData.java | 0 .../data/properties/PropEnchantStore.java | 0 .../data/properties/PropEnchantments.java | 0 .../data/properties/PropEquipItem.java | 0 .../data/properties/PropFireworkData.java | 0 .../ru/abstractmenus/data/properties/PropFlags.java | 0 .../ru/abstractmenus/data/properties/PropGlow.java | 0 .../ru/abstractmenus/data/properties/PropHDB.java | 0 .../data/properties/PropItemsAdder.java | 0 .../data/properties/PropKnowledgeBook.java | 0 .../abstractmenus/data/properties/PropLPMeta.java | 0 .../ru/abstractmenus/data/properties/PropLore.java | 0 .../data/properties/PropLoreLight.java | 0 .../abstractmenus/data/properties/PropMaterial.java | 0 .../abstractmenus/data/properties/PropMmoItem.java | 0 .../ru/abstractmenus/data/properties/PropModel.java | 0 .../ru/abstractmenus/data/properties/PropName.java | 0 .../data/properties/PropNameLight.java | 0 .../ru/abstractmenus/data/properties/PropNbt.java | 0 .../abstractmenus/data/properties/PropOraxen.java | 0 .../data/properties/PropPotionData.java | 0 .../data/properties/PropSerialized.java | 0 .../data/properties/PropShieldData.java | 0 .../data/properties/PropSkullOwner.java | 0 .../abstractmenus/data/properties/PropTexture.java | 0 .../data/properties/PropUnbreakable.java | 0 .../java/ru/abstractmenus/data/rules/MenuRules.java | 0 .../data/rules/RuleBungeeIsOnline.java | 0 .../abstractmenus/data/rules/RuleBungeeOnline.java | 0 .../ru/abstractmenus/data/rules/RuleChance.java | 0 .../ru/abstractmenus/data/rules/RuleExistVar.java | 0 .../ru/abstractmenus/data/rules/RuleExistVarp.java | 0 .../ru/abstractmenus/data/rules/RuleFoodLevel.java | 0 .../ru/abstractmenus/data/rules/RuleFreeSlot.java | 0 .../abstractmenus/data/rules/RuleFreeSlotCount.java | 0 .../ru/abstractmenus/data/rules/RuleGameMode.java | 0 .../java/ru/abstractmenus/data/rules/RuleGroup.java | 0 .../ru/abstractmenus/data/rules/RuleHealth.java | 0 .../ru/abstractmenus/data/rules/RuleHeldItem.java | 0 .../java/ru/abstractmenus/data/rules/RuleIf.java | 0 .../abstractmenus/data/rules/RuleInventoryItem.java | 0 .../java/ru/abstractmenus/data/rules/RuleJS.java | 0 .../java/ru/abstractmenus/data/rules/RuleLevel.java | 0 .../java/ru/abstractmenus/data/rules/RuleMoney.java | 0 .../ru/abstractmenus/data/rules/RuleOnline.java | 0 .../ru/abstractmenus/data/rules/RulePermission.java | 0 .../ru/abstractmenus/data/rules/RulePlacedItem.java | 0 .../data/rules/RulePlayerIsOnline.java | 0 .../ru/abstractmenus/data/rules/RuleRegion.java | 0 .../java/ru/abstractmenus/data/rules/RuleWorld.java | 0 .../java/ru/abstractmenus/data/rules/RuleXp.java | 0 .../abstractmenus/data/rules/logical/RuleAnd.java | 0 .../abstractmenus/data/rules/logical/RuleNot.java | 0 .../abstractmenus/data/rules/logical/RuleOneOf.java | 0 .../ru/abstractmenus/data/rules/logical/RuleOr.java | 0 .../data/rules/logical/RulePlayerScope.java | 0 .../data/rules/logical/RulesGroup.java | 0 .../java/ru/abstractmenus/datatype/DataType.java | 0 .../java/ru/abstractmenus/datatype/TypeBool.java | 0 .../java/ru/abstractmenus/datatype/TypeByte.java | 0 .../java/ru/abstractmenus/datatype/TypeColor.java | 0 .../java/ru/abstractmenus/datatype/TypeDouble.java | 0 .../java/ru/abstractmenus/datatype/TypeEnum.java | 0 .../java/ru/abstractmenus/datatype/TypeFloat.java | 0 .../java/ru/abstractmenus/datatype/TypeInt.java | 0 .../ru/abstractmenus/datatype/TypeLocation.java | 0 .../java/ru/abstractmenus/datatype/TypeLong.java | 0 .../ru/abstractmenus/datatype/TypeMaterial.java | 0 .../java/ru/abstractmenus/datatype/TypeShort.java | 0 .../java/ru/abstractmenus/datatype/TypeSlot.java | 0 .../java/ru/abstractmenus/datatype/TypeSound.java | 0 .../java/ru/abstractmenus/datatype/TypeWorld.java | 0 .../ru/abstractmenus/events/RegionEnterEvent.java | 0 .../java/ru/abstractmenus/events/RegionEvent.java | 0 .../ru/abstractmenus/events/RegionLeaveEvent.java | 0 .../ru/abstractmenus/extractors/BlockExtractor.java | 0 .../abstractmenus/extractors/CommandExtractor.java | 0 .../abstractmenus/extractors/EntityExtractor.java | 0 .../extractors/ItemStackExtractor.java | 0 .../ru/abstractmenus/extractors/NPCExtractor.java | 0 .../abstractmenus/extractors/PlayerExtractor.java | 0 .../abstractmenus/extractors/RegionExtractor.java | 0 .../ru/abstractmenus/extractors/WorldExtractor.java | 0 .../abstractmenus/handlers/EconomyVaultHandler.java | 0 .../abstractmenus/handlers/LevelDefaultHandler.java | 0 .../ru/abstractmenus/handlers/LuckPermsHandler.java | 0 .../handlers/PermissionDefaultHandler.java | 0 .../handlers/SkinsRestorerHandler.java | 0 .../placeholder/PlaceholderCustomHandler.java | 0 .../placeholder/PlaceholderDefaultHandler.java | 0 .../ru/abstractmenus/listeners/ChatListener.java | 0 .../abstractmenus/listeners/InventoryListener.java | 0 .../ru/abstractmenus/listeners/PlayerListener.java | 0 .../listeners/wg/PlayerMoveListener.java | 0 .../ru/abstractmenus/listeners/wg/WGHandlers.java | 0 .../abstractmenus/listeners/wg/WgRegionHandler.java | 0 .../java/ru/abstractmenus/menu/AbstractMenu.java | 0 .../java/ru/abstractmenus/menu/MenuListener.java | 0 .../main/java/ru/abstractmenus/menu/SimpleMenu.java | 0 .../abstractmenus/menu/animated/AnimatedMenu.java | 0 .../java/ru/abstractmenus/menu/animated/Frame.java | 0 .../abstractmenus/menu/generated/GeneratedMenu.java | 0 .../ru/abstractmenus/menu/generated/Matrix.java | 0 .../ru/abstractmenus/menu/item/InventoryItem.java | 0 .../java/ru/abstractmenus/menu/item/MenuItem.java | 0 .../java/ru/abstractmenus/menu/item/SimpleItem.java | 0 .../ru/abstractmenus/nms/actionbar/ActionBar.java | 0 .../abstractmenus/nms/actionbar/ActionBar_1_9.java | 0 .../nms/actionbar/ActionBar_PaperMc.java | 0 .../main/java/ru/abstractmenus/nms/book/Book.java | 0 .../java/ru/abstractmenus/nms/book/Book_1_15.java | 0 .../ru/abstractmenus/nms/title/SenderModern.java | 0 .../main/java/ru/abstractmenus/nms/title/Title.java | 0 .../ru/abstractmenus/nms/title/TitleSender.java | 0 .../placeholders/PAPIPlaceholders.java | 0 .../abstractmenus/placeholders/PlaceholderHook.java | 0 .../placeholders/hooks/ActivatorPlaceholders.java | 0 .../placeholders/hooks/BungeePlaceholders.java | 0 .../placeholders/hooks/CatalogPlaceholders.java | 0 .../placeholders/hooks/HeadAnimPlaceholders.java | 0 .../placeholders/hooks/PlayerPlaceholders.java | 0 .../placeholders/hooks/ServerPlaceholders.java | 0 .../placeholders/hooks/VarPlaceholders.java | 0 .../hooks/dnd/ChangedItemPlaceholders.java | 0 .../hooks/dnd/PlacedItemPlaceholders.java | 0 .../hooks/dnd/TakenItemPlaceholders.java | 0 .../serializers/BannerPatternSerializer.java | 0 .../abstractmenus/serializers/ColorSerializer.java | 0 .../serializers/EntityTypeSerializer.java | 0 .../serializers/FireworkEffectSerializer.java | 0 .../abstractmenus/serializers/ItemSerializer.java | 0 .../abstractmenus/serializers/JsonSerializer.java | 0 .../serializers/LocationSerializer.java | 0 .../serializers/NbtCompoundSerializer.java | 0 .../serializers/PotionEffectSerializer.java | 0 .../ru/abstractmenus/serializers/Serializers.java | 0 .../serializers/ShapedRecipeSerializer.java | 0 .../abstractmenus/serializers/WorldSerializer.java | 0 .../serializers/menu/AnimatedMenuSerializer.java | 0 .../serializers/menu/BaseMenuSerializer.java | 0 .../serializers/menu/GeneratedMenuSerializer.java | 0 .../serializers/menu/MenuSerializer.java | 0 .../serializers/menu/SimpleMenuSerializer.java | 0 .../ru/abstractmenus/services/BungeeManager.java | 0 .../ru/abstractmenus/services/HeadAnimManager.java | 0 .../java/ru/abstractmenus/services/MenuManager.java | 0 .../ru/abstractmenus/services/ProfileStorage.java | 0 .../services/proxy/dto/ChangeSkinDTO.java | 0 .../ru/abstractmenus/util/ArrayListIterator.java | 0 .../main/java/ru/abstractmenus/util/FileUtils.java | 0 .../abstractmenus/util/LegacyColorTagReplacer.java | 0 .../java/ru/abstractmenus/util/MiniMessageUtil.java | 0 .../src}/main/java/ru/abstractmenus/util/NMS.java | 0 .../main/java/ru/abstractmenus/util/NumberUtil.java | 0 .../java/ru/abstractmenus/util/RegionUtils.java | 0 .../main/java/ru/abstractmenus/util/SlotUtil.java | 0 .../main/java/ru/abstractmenus/util/StringUtil.java | 0 .../main/java/ru/abstractmenus/util/TimeParser.java | 0 .../main/java/ru/abstractmenus/util/TimeUtil.java | 0 .../src}/main/java/ru/abstractmenus/util/Tuple.java | 0 .../ru/abstractmenus/util/bukkit/BukkitTasks.java | 0 .../java/ru/abstractmenus/util/bukkit/Events.java | 0 .../java/ru/abstractmenus/util/bukkit/ItemUtil.java | 0 .../ru/abstractmenus/util/bukkit/MojangApi.java | 0 .../java/ru/abstractmenus/util/bukkit/Skulls.java | 0 .../ru/abstractmenus/util/bukkit/TaskHandle.java | 0 .../java/ru/abstractmenus/util/bukkit/UuidUtil.java | 0 .../java/ru/abstractmenus/util/proxy/ClassInfo.java | 0 .../ru/abstractmenus/util/proxy/ReflectionUtil.java | 0 .../ru/abstractmenus/variables/VarBuilderImpl.java | 0 .../java/ru/abstractmenus/variables/VarData.java | 0 .../java/ru/abstractmenus/variables/VarImpl.java | 0 .../java/ru/abstractmenus/variables/VarNumData.java | 0 .../variables/VariableManagerImpl.java | 0 .../ru/abstractmenus/variables/VariablesDao.java | 0 .../src}/main/resources/1_13/menu_anim.conf | 0 .../src}/main/resources/1_21/menu_anim.conf | 0 {src => plugin/src}/main/resources/LICENSES | 0 .../src}/main/resources/animated_heads.conf | 0 .../src}/main/resources/bench-menus/README.md | 0 .../src}/main/resources/bench-menus/_templates.conf | 0 .../resources/bench-menus/bench_actions_rich.conf | 0 .../bench-menus/bench_animated_carousel.conf | 0 .../resources/bench-menus/bench_complex_rules.conf | 0 .../resources/bench-menus/bench_dynamic_lore.conf | 0 .../bench-menus/bench_generated_pagination.conf | 0 .../resources/bench-menus/bench_high_refresh.conf | 0 .../bench-menus/bench_minimessage_heavy.conf | 0 .../resources/bench-menus/bench_skull_grid.conf | 0 .../resources/bench-menus/bench_static_grid.conf | 0 .../main/resources/bench-menus/bench_variables.conf | 0 {src => plugin/src}/main/resources/config.conf | 0 {src => plugin/src}/main/resources/menu.conf | 0 {src => plugin/src}/main/resources/menu_anim.conf | 0 {src => plugin/src}/main/resources/plugin.yml | 0 {src => plugin/src}/main/resources/variables.sql | 0 .../abstractmenus/bench/ConcurrencyBenchmark.java | 0 .../ru/abstractmenus/bench/ItemBuildBenchmark.java | 0 .../bench/PlaceholderReplaceBenchmark.java | 0 .../java/ru/abstractmenus/bench/RunBenchmarks.java | 0 .../abstractmenus/bench/SlotContainsBenchmark.java | 0 .../abstractmenus/bench/StringSplitBenchmark.java | 0 .../data/actions/TestActionCommandBehavior.java | 0 .../data/properties/TestPropNameLorePrecompute.java | 0 .../datatype/TestCompoundDataTypes.java | 0 .../abstractmenus/datatype/TestHasPlaceholder.java | 0 .../datatype/TestPrimitiveDataTypes.java | 0 .../ru/abstractmenus/datatype/TestTypeMaterial.java | 0 .../datatype/TestTypeMaterialSerializer.java | 0 .../integration/TestPluginLifecycle.java | 0 .../menu/item/TestSimpleItemCopyOnWrite.java | 0 .../serializers/TestColorSerializer.java | 0 .../serializers/TestEntityTypeSerializer.java | 0 .../serializers/TestJsonSerializer.java | 0 .../java/ru/abstractmenus/util/TestFileUtils.java | 0 .../util/TestLegacyColorTagReplacer.java | 0 .../java/ru/abstractmenus/util/TestNumberUtil.java | 0 .../java/ru/abstractmenus/util/TestSlotUtil.java | 0 .../java/ru/abstractmenus/util/TestStringUtil.java | 0 .../java/ru/abstractmenus/util/TestTimeParser.java | 0 {src => plugin/src}/test/resources/test.conf | 0 373 files changed, 0 insertions(+), 0 deletions(-) rename build.gradle => plugin/build.gradle (100%) rename {libs => plugin/libs}/MMOItems-6.5.4.jar (100%) rename {libs => plugin/libs}/citizens-2.0.jar (100%) rename {libs => plugin/libs}/paper-1.20.6-147.jar (100%) rename proguard.txt => plugin/proguard.txt (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/AbstractMenus.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/Constants.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/MainConfig.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/Metrics.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/command/ArgumentParseException.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/command/Command.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/command/CommandContext.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/command/CommandHandler.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/command/CommandManager.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/command/DefaultHandler.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/command/args/Argument.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/command/args/ChoiceArgument.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/command/args/IntegerArgument.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/command/args/NumberArgument.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/command/args/PlayerArgument.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/command/args/StringArgument.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/command/bukkit/CommandWrapper.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/commands/AbstractMenuCommand.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/commands/Command.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/commands/VarCommand.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/commands/VarpCommand.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/commands/am/CommandOpen.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/commands/am/CommandPluginVersion.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/commands/am/CommandReload.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/commands/am/CommandServe.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/commands/var/VarDec.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/commands/var/VarDiv.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/commands/var/VarGet.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/commands/var/VarInc.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/commands/var/VarMul.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/commands/var/VarRem.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/commands/var/VarSet.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/commands/varp/VarpDec.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/commands/varp/VarpDiv.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/commands/varp/VarpGet.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/commands/varp/VarpInc.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/commands/varp/VarpMul.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/commands/varp/VarpRem.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/commands/varp/VarpSet.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/Actions.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/BannerData.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/BookData.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/EntityData.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionBookOpen.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionBroadcast.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionBungeeConnect.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionButtonRemove.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionButtonSet.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionCommand.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionFoodLevelSet.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionGameModeSet.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionGroupAdd.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionGroupRemove.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionHealthSet.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionInputChat.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionInventoryClear.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionItemAdd.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionItemClear.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionItemRefresh.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionItemRemove.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionLevelGive.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionLevelTake.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionLog.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionLuckPermsMetaRemove.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionLuckPermsMetaSet.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionMenuClose.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionMenuOpen.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionMenuOpenCtx.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionMenuRefresh.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionMessage.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionMiniMessage.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionMoneyGive.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionMoneyTake.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionPageNext.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionPagePrev.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionPermissionGive.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionPermissionRemove.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionPlaceItem.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionPlacedItemRemove.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionPlayerChat.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionPotionEffect.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionPotionEffectRemove.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionPropertyRemove.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionPropertySet.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionRecipeAdd.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionSkinReset.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionSkinSet.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionSound.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionSoundCustom.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionTeleport.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionXpGive.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/ActionXpTake.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/MenuActions.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/var/ActionVarDec.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/var/ActionVarDiv.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/var/ActionVarInc.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/var/ActionVarMul.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/var/ActionVarRem.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/var/ActionVarSet.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/varp/ActionVarpDec.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/varp/ActionVarpDiv.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/varp/ActionVarpInc.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/varp/ActionVarpMul.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/varp/ActionVarpRem.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/varp/ActionVarpSet.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/wrappers/ActionBulk.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/wrappers/ActionDelay.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/wrappers/ActionPlayerScope.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/actions/wrappers/ActionRandomActions.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/activators/ActivatorUtil.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/activators/Activators.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/activators/OpenButton.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/activators/OpenChat.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/activators/OpenChatContains.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/activators/OpenClickBlock.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/activators/OpenClickBlockType.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/activators/OpenClickEntity.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/activators/OpenClickItem.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/activators/OpenClickNPC.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/activators/OpenCommand.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/activators/OpenJoin.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/activators/OpenLever.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/activators/OpenPlate.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/activators/OpenRegionEnter.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/activators/OpenRegionLeave.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/activators/OpenShiftClickEntity.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/activators/OpenSign.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/activators/OpenSwapItems.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/catalogs/Catalogs.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/catalogs/EntityCatalog.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/catalogs/IteratorCatalog.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/catalogs/PlayerCatalog.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/catalogs/ServerCatalog.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/catalogs/SliceCatalog.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/catalogs/WorldCatalog.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/comparator/BooleanEvaluator.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/comparator/LegacyValueComparator.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/comparator/ModernValueComparator.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/comparator/ValueComparator.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/properties/ItemProps.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/properties/PropAttributeModifier.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/properties/PropBannerData.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/properties/PropBindings.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/properties/PropBookData.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/properties/PropColor.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/properties/PropCount.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/properties/PropDamage.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/properties/PropData.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/properties/PropEnchantStore.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/properties/PropEnchantments.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/properties/PropEquipItem.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/properties/PropFireworkData.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/properties/PropFlags.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/properties/PropGlow.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/properties/PropHDB.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/properties/PropItemsAdder.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/properties/PropKnowledgeBook.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/properties/PropLPMeta.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/properties/PropLore.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/properties/PropLoreLight.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/properties/PropMaterial.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/properties/PropMmoItem.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/properties/PropModel.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/properties/PropName.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/properties/PropNameLight.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/properties/PropNbt.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/properties/PropOraxen.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/properties/PropPotionData.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/properties/PropSerialized.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/properties/PropShieldData.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/properties/PropSkullOwner.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/properties/PropTexture.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/properties/PropUnbreakable.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/rules/MenuRules.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/rules/RuleBungeeIsOnline.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/rules/RuleBungeeOnline.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/rules/RuleChance.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/rules/RuleExistVar.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/rules/RuleExistVarp.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/rules/RuleFoodLevel.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/rules/RuleFreeSlot.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/rules/RuleFreeSlotCount.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/rules/RuleGameMode.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/rules/RuleGroup.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/rules/RuleHealth.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/rules/RuleHeldItem.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/rules/RuleIf.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/rules/RuleInventoryItem.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/rules/RuleJS.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/rules/RuleLevel.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/rules/RuleMoney.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/rules/RuleOnline.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/rules/RulePermission.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/rules/RulePlacedItem.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/rules/RulePlayerIsOnline.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/rules/RuleRegion.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/rules/RuleWorld.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/rules/RuleXp.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/rules/logical/RuleAnd.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/rules/logical/RuleNot.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/rules/logical/RuleOneOf.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/rules/logical/RuleOr.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/rules/logical/RulePlayerScope.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/data/rules/logical/RulesGroup.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/datatype/DataType.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/datatype/TypeBool.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/datatype/TypeByte.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/datatype/TypeColor.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/datatype/TypeDouble.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/datatype/TypeEnum.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/datatype/TypeFloat.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/datatype/TypeInt.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/datatype/TypeLocation.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/datatype/TypeLong.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/datatype/TypeMaterial.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/datatype/TypeShort.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/datatype/TypeSlot.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/datatype/TypeSound.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/datatype/TypeWorld.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/events/RegionEnterEvent.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/events/RegionEvent.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/events/RegionLeaveEvent.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/extractors/BlockExtractor.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/extractors/CommandExtractor.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/extractors/EntityExtractor.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/extractors/ItemStackExtractor.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/extractors/NPCExtractor.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/extractors/PlayerExtractor.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/extractors/RegionExtractor.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/extractors/WorldExtractor.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/handlers/EconomyVaultHandler.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/handlers/LevelDefaultHandler.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/handlers/LuckPermsHandler.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/handlers/PermissionDefaultHandler.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/handlers/SkinsRestorerHandler.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/handlers/placeholder/PlaceholderCustomHandler.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/handlers/placeholder/PlaceholderDefaultHandler.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/listeners/ChatListener.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/listeners/InventoryListener.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/listeners/PlayerListener.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/listeners/wg/PlayerMoveListener.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/listeners/wg/WGHandlers.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/listeners/wg/WgRegionHandler.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/menu/AbstractMenu.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/menu/MenuListener.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/menu/SimpleMenu.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/menu/animated/AnimatedMenu.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/menu/animated/Frame.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/menu/generated/GeneratedMenu.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/menu/generated/Matrix.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/menu/item/InventoryItem.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/menu/item/MenuItem.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/menu/item/SimpleItem.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/nms/actionbar/ActionBar.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/nms/actionbar/ActionBar_1_9.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/nms/actionbar/ActionBar_PaperMc.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/nms/book/Book.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/nms/book/Book_1_15.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/nms/title/SenderModern.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/nms/title/Title.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/nms/title/TitleSender.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/placeholders/PAPIPlaceholders.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/placeholders/PlaceholderHook.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/placeholders/hooks/ActivatorPlaceholders.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/placeholders/hooks/BungeePlaceholders.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/placeholders/hooks/CatalogPlaceholders.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/placeholders/hooks/HeadAnimPlaceholders.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/placeholders/hooks/PlayerPlaceholders.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/placeholders/hooks/ServerPlaceholders.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/placeholders/hooks/VarPlaceholders.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/placeholders/hooks/dnd/ChangedItemPlaceholders.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/placeholders/hooks/dnd/PlacedItemPlaceholders.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/placeholders/hooks/dnd/TakenItemPlaceholders.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/serializers/BannerPatternSerializer.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/serializers/ColorSerializer.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/serializers/EntityTypeSerializer.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/serializers/FireworkEffectSerializer.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/serializers/ItemSerializer.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/serializers/JsonSerializer.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/serializers/LocationSerializer.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/serializers/NbtCompoundSerializer.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/serializers/PotionEffectSerializer.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/serializers/Serializers.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/serializers/ShapedRecipeSerializer.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/serializers/WorldSerializer.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/serializers/menu/AnimatedMenuSerializer.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/serializers/menu/BaseMenuSerializer.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/serializers/menu/GeneratedMenuSerializer.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/serializers/menu/MenuSerializer.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/serializers/menu/SimpleMenuSerializer.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/services/BungeeManager.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/services/HeadAnimManager.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/services/MenuManager.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/services/ProfileStorage.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/services/proxy/dto/ChangeSkinDTO.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/util/ArrayListIterator.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/util/FileUtils.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/util/LegacyColorTagReplacer.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/util/MiniMessageUtil.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/util/NMS.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/util/NumberUtil.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/util/RegionUtils.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/util/SlotUtil.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/util/StringUtil.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/util/TimeParser.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/util/TimeUtil.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/util/Tuple.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/util/bukkit/BukkitTasks.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/util/bukkit/Events.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/util/bukkit/ItemUtil.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/util/bukkit/MojangApi.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/util/bukkit/Skulls.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/util/bukkit/TaskHandle.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/util/bukkit/UuidUtil.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/util/proxy/ClassInfo.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/util/proxy/ReflectionUtil.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/variables/VarBuilderImpl.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/variables/VarData.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/variables/VarImpl.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/variables/VarNumData.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/variables/VariableManagerImpl.java (100%) rename {src => plugin/src}/main/java/ru/abstractmenus/variables/VariablesDao.java (100%) rename {src => plugin/src}/main/resources/1_13/menu_anim.conf (100%) rename {src => plugin/src}/main/resources/1_21/menu_anim.conf (100%) rename {src => plugin/src}/main/resources/LICENSES (100%) rename {src => plugin/src}/main/resources/animated_heads.conf (100%) rename {src => plugin/src}/main/resources/bench-menus/README.md (100%) rename {src => plugin/src}/main/resources/bench-menus/_templates.conf (100%) rename {src => plugin/src}/main/resources/bench-menus/bench_actions_rich.conf (100%) rename {src => plugin/src}/main/resources/bench-menus/bench_animated_carousel.conf (100%) rename {src => plugin/src}/main/resources/bench-menus/bench_complex_rules.conf (100%) rename {src => plugin/src}/main/resources/bench-menus/bench_dynamic_lore.conf (100%) rename {src => plugin/src}/main/resources/bench-menus/bench_generated_pagination.conf (100%) rename {src => plugin/src}/main/resources/bench-menus/bench_high_refresh.conf (100%) rename {src => plugin/src}/main/resources/bench-menus/bench_minimessage_heavy.conf (100%) rename {src => plugin/src}/main/resources/bench-menus/bench_skull_grid.conf (100%) rename {src => plugin/src}/main/resources/bench-menus/bench_static_grid.conf (100%) rename {src => plugin/src}/main/resources/bench-menus/bench_variables.conf (100%) rename {src => plugin/src}/main/resources/config.conf (100%) rename {src => plugin/src}/main/resources/menu.conf (100%) rename {src => plugin/src}/main/resources/menu_anim.conf (100%) rename {src => plugin/src}/main/resources/plugin.yml (100%) rename {src => plugin/src}/main/resources/variables.sql (100%) rename {src => plugin/src}/test/java/ru/abstractmenus/bench/ConcurrencyBenchmark.java (100%) rename {src => plugin/src}/test/java/ru/abstractmenus/bench/ItemBuildBenchmark.java (100%) rename {src => plugin/src}/test/java/ru/abstractmenus/bench/PlaceholderReplaceBenchmark.java (100%) rename {src => plugin/src}/test/java/ru/abstractmenus/bench/RunBenchmarks.java (100%) rename {src => plugin/src}/test/java/ru/abstractmenus/bench/SlotContainsBenchmark.java (100%) rename {src => plugin/src}/test/java/ru/abstractmenus/bench/StringSplitBenchmark.java (100%) rename {src => plugin/src}/test/java/ru/abstractmenus/data/actions/TestActionCommandBehavior.java (100%) rename {src => plugin/src}/test/java/ru/abstractmenus/data/properties/TestPropNameLorePrecompute.java (100%) rename {src => plugin/src}/test/java/ru/abstractmenus/datatype/TestCompoundDataTypes.java (100%) rename {src => plugin/src}/test/java/ru/abstractmenus/datatype/TestHasPlaceholder.java (100%) rename {src => plugin/src}/test/java/ru/abstractmenus/datatype/TestPrimitiveDataTypes.java (100%) rename {src => plugin/src}/test/java/ru/abstractmenus/datatype/TestTypeMaterial.java (100%) rename {src => plugin/src}/test/java/ru/abstractmenus/datatype/TestTypeMaterialSerializer.java (100%) rename {src => plugin/src}/test/java/ru/abstractmenus/integration/TestPluginLifecycle.java (100%) rename {src => plugin/src}/test/java/ru/abstractmenus/menu/item/TestSimpleItemCopyOnWrite.java (100%) rename {src => plugin/src}/test/java/ru/abstractmenus/serializers/TestColorSerializer.java (100%) rename {src => plugin/src}/test/java/ru/abstractmenus/serializers/TestEntityTypeSerializer.java (100%) rename {src => plugin/src}/test/java/ru/abstractmenus/serializers/TestJsonSerializer.java (100%) rename {src => plugin/src}/test/java/ru/abstractmenus/util/TestFileUtils.java (100%) rename {src => plugin/src}/test/java/ru/abstractmenus/util/TestLegacyColorTagReplacer.java (100%) rename {src => plugin/src}/test/java/ru/abstractmenus/util/TestNumberUtil.java (100%) rename {src => plugin/src}/test/java/ru/abstractmenus/util/TestSlotUtil.java (100%) rename {src => plugin/src}/test/java/ru/abstractmenus/util/TestStringUtil.java (100%) rename {src => plugin/src}/test/java/ru/abstractmenus/util/TestTimeParser.java (100%) rename {src => plugin/src}/test/resources/test.conf (100%) diff --git a/build.gradle b/plugin/build.gradle similarity index 100% rename from build.gradle rename to plugin/build.gradle diff --git a/libs/MMOItems-6.5.4.jar b/plugin/libs/MMOItems-6.5.4.jar similarity index 100% rename from libs/MMOItems-6.5.4.jar rename to plugin/libs/MMOItems-6.5.4.jar diff --git a/libs/citizens-2.0.jar b/plugin/libs/citizens-2.0.jar similarity index 100% rename from libs/citizens-2.0.jar rename to plugin/libs/citizens-2.0.jar diff --git a/libs/paper-1.20.6-147.jar b/plugin/libs/paper-1.20.6-147.jar similarity index 100% rename from libs/paper-1.20.6-147.jar rename to plugin/libs/paper-1.20.6-147.jar diff --git a/proguard.txt b/plugin/proguard.txt similarity index 100% rename from proguard.txt rename to plugin/proguard.txt diff --git a/src/main/java/ru/abstractmenus/AbstractMenus.java b/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java similarity index 100% rename from src/main/java/ru/abstractmenus/AbstractMenus.java rename to plugin/src/main/java/ru/abstractmenus/AbstractMenus.java diff --git a/src/main/java/ru/abstractmenus/Constants.java b/plugin/src/main/java/ru/abstractmenus/Constants.java similarity index 100% rename from src/main/java/ru/abstractmenus/Constants.java rename to plugin/src/main/java/ru/abstractmenus/Constants.java diff --git a/src/main/java/ru/abstractmenus/MainConfig.java b/plugin/src/main/java/ru/abstractmenus/MainConfig.java similarity index 100% rename from src/main/java/ru/abstractmenus/MainConfig.java rename to plugin/src/main/java/ru/abstractmenus/MainConfig.java diff --git a/src/main/java/ru/abstractmenus/Metrics.java b/plugin/src/main/java/ru/abstractmenus/Metrics.java similarity index 100% rename from src/main/java/ru/abstractmenus/Metrics.java rename to plugin/src/main/java/ru/abstractmenus/Metrics.java diff --git a/src/main/java/ru/abstractmenus/command/ArgumentParseException.java b/plugin/src/main/java/ru/abstractmenus/command/ArgumentParseException.java similarity index 100% rename from src/main/java/ru/abstractmenus/command/ArgumentParseException.java rename to plugin/src/main/java/ru/abstractmenus/command/ArgumentParseException.java diff --git a/src/main/java/ru/abstractmenus/command/Command.java b/plugin/src/main/java/ru/abstractmenus/command/Command.java similarity index 100% rename from src/main/java/ru/abstractmenus/command/Command.java rename to plugin/src/main/java/ru/abstractmenus/command/Command.java diff --git a/src/main/java/ru/abstractmenus/command/CommandContext.java b/plugin/src/main/java/ru/abstractmenus/command/CommandContext.java similarity index 100% rename from src/main/java/ru/abstractmenus/command/CommandContext.java rename to plugin/src/main/java/ru/abstractmenus/command/CommandContext.java diff --git a/src/main/java/ru/abstractmenus/command/CommandHandler.java b/plugin/src/main/java/ru/abstractmenus/command/CommandHandler.java similarity index 100% rename from src/main/java/ru/abstractmenus/command/CommandHandler.java rename to plugin/src/main/java/ru/abstractmenus/command/CommandHandler.java diff --git a/src/main/java/ru/abstractmenus/command/CommandManager.java b/plugin/src/main/java/ru/abstractmenus/command/CommandManager.java similarity index 100% rename from src/main/java/ru/abstractmenus/command/CommandManager.java rename to plugin/src/main/java/ru/abstractmenus/command/CommandManager.java diff --git a/src/main/java/ru/abstractmenus/command/DefaultHandler.java b/plugin/src/main/java/ru/abstractmenus/command/DefaultHandler.java similarity index 100% rename from src/main/java/ru/abstractmenus/command/DefaultHandler.java rename to plugin/src/main/java/ru/abstractmenus/command/DefaultHandler.java diff --git a/src/main/java/ru/abstractmenus/command/args/Argument.java b/plugin/src/main/java/ru/abstractmenus/command/args/Argument.java similarity index 100% rename from src/main/java/ru/abstractmenus/command/args/Argument.java rename to plugin/src/main/java/ru/abstractmenus/command/args/Argument.java diff --git a/src/main/java/ru/abstractmenus/command/args/ChoiceArgument.java b/plugin/src/main/java/ru/abstractmenus/command/args/ChoiceArgument.java similarity index 100% rename from src/main/java/ru/abstractmenus/command/args/ChoiceArgument.java rename to plugin/src/main/java/ru/abstractmenus/command/args/ChoiceArgument.java diff --git a/src/main/java/ru/abstractmenus/command/args/IntegerArgument.java b/plugin/src/main/java/ru/abstractmenus/command/args/IntegerArgument.java similarity index 100% rename from src/main/java/ru/abstractmenus/command/args/IntegerArgument.java rename to plugin/src/main/java/ru/abstractmenus/command/args/IntegerArgument.java diff --git a/src/main/java/ru/abstractmenus/command/args/NumberArgument.java b/plugin/src/main/java/ru/abstractmenus/command/args/NumberArgument.java similarity index 100% rename from src/main/java/ru/abstractmenus/command/args/NumberArgument.java rename to plugin/src/main/java/ru/abstractmenus/command/args/NumberArgument.java diff --git a/src/main/java/ru/abstractmenus/command/args/PlayerArgument.java b/plugin/src/main/java/ru/abstractmenus/command/args/PlayerArgument.java similarity index 100% rename from src/main/java/ru/abstractmenus/command/args/PlayerArgument.java rename to plugin/src/main/java/ru/abstractmenus/command/args/PlayerArgument.java diff --git a/src/main/java/ru/abstractmenus/command/args/StringArgument.java b/plugin/src/main/java/ru/abstractmenus/command/args/StringArgument.java similarity index 100% rename from src/main/java/ru/abstractmenus/command/args/StringArgument.java rename to plugin/src/main/java/ru/abstractmenus/command/args/StringArgument.java diff --git a/src/main/java/ru/abstractmenus/command/bukkit/CommandWrapper.java b/plugin/src/main/java/ru/abstractmenus/command/bukkit/CommandWrapper.java similarity index 100% rename from src/main/java/ru/abstractmenus/command/bukkit/CommandWrapper.java rename to plugin/src/main/java/ru/abstractmenus/command/bukkit/CommandWrapper.java diff --git a/src/main/java/ru/abstractmenus/commands/AbstractMenuCommand.java b/plugin/src/main/java/ru/abstractmenus/commands/AbstractMenuCommand.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/AbstractMenuCommand.java rename to plugin/src/main/java/ru/abstractmenus/commands/AbstractMenuCommand.java diff --git a/src/main/java/ru/abstractmenus/commands/Command.java b/plugin/src/main/java/ru/abstractmenus/commands/Command.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/Command.java rename to plugin/src/main/java/ru/abstractmenus/commands/Command.java diff --git a/src/main/java/ru/abstractmenus/commands/VarCommand.java b/plugin/src/main/java/ru/abstractmenus/commands/VarCommand.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/VarCommand.java rename to plugin/src/main/java/ru/abstractmenus/commands/VarCommand.java diff --git a/src/main/java/ru/abstractmenus/commands/VarpCommand.java b/plugin/src/main/java/ru/abstractmenus/commands/VarpCommand.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/VarpCommand.java rename to plugin/src/main/java/ru/abstractmenus/commands/VarpCommand.java diff --git a/src/main/java/ru/abstractmenus/commands/am/CommandOpen.java b/plugin/src/main/java/ru/abstractmenus/commands/am/CommandOpen.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/am/CommandOpen.java rename to plugin/src/main/java/ru/abstractmenus/commands/am/CommandOpen.java diff --git a/src/main/java/ru/abstractmenus/commands/am/CommandPluginVersion.java b/plugin/src/main/java/ru/abstractmenus/commands/am/CommandPluginVersion.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/am/CommandPluginVersion.java rename to plugin/src/main/java/ru/abstractmenus/commands/am/CommandPluginVersion.java diff --git a/src/main/java/ru/abstractmenus/commands/am/CommandReload.java b/plugin/src/main/java/ru/abstractmenus/commands/am/CommandReload.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/am/CommandReload.java rename to plugin/src/main/java/ru/abstractmenus/commands/am/CommandReload.java diff --git a/src/main/java/ru/abstractmenus/commands/am/CommandServe.java b/plugin/src/main/java/ru/abstractmenus/commands/am/CommandServe.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/am/CommandServe.java rename to plugin/src/main/java/ru/abstractmenus/commands/am/CommandServe.java diff --git a/src/main/java/ru/abstractmenus/commands/var/VarDec.java b/plugin/src/main/java/ru/abstractmenus/commands/var/VarDec.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/var/VarDec.java rename to plugin/src/main/java/ru/abstractmenus/commands/var/VarDec.java diff --git a/src/main/java/ru/abstractmenus/commands/var/VarDiv.java b/plugin/src/main/java/ru/abstractmenus/commands/var/VarDiv.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/var/VarDiv.java rename to plugin/src/main/java/ru/abstractmenus/commands/var/VarDiv.java diff --git a/src/main/java/ru/abstractmenus/commands/var/VarGet.java b/plugin/src/main/java/ru/abstractmenus/commands/var/VarGet.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/var/VarGet.java rename to plugin/src/main/java/ru/abstractmenus/commands/var/VarGet.java diff --git a/src/main/java/ru/abstractmenus/commands/var/VarInc.java b/plugin/src/main/java/ru/abstractmenus/commands/var/VarInc.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/var/VarInc.java rename to plugin/src/main/java/ru/abstractmenus/commands/var/VarInc.java diff --git a/src/main/java/ru/abstractmenus/commands/var/VarMul.java b/plugin/src/main/java/ru/abstractmenus/commands/var/VarMul.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/var/VarMul.java rename to plugin/src/main/java/ru/abstractmenus/commands/var/VarMul.java diff --git a/src/main/java/ru/abstractmenus/commands/var/VarRem.java b/plugin/src/main/java/ru/abstractmenus/commands/var/VarRem.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/var/VarRem.java rename to plugin/src/main/java/ru/abstractmenus/commands/var/VarRem.java diff --git a/src/main/java/ru/abstractmenus/commands/var/VarSet.java b/plugin/src/main/java/ru/abstractmenus/commands/var/VarSet.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/var/VarSet.java rename to plugin/src/main/java/ru/abstractmenus/commands/var/VarSet.java diff --git a/src/main/java/ru/abstractmenus/commands/varp/VarpDec.java b/plugin/src/main/java/ru/abstractmenus/commands/varp/VarpDec.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/varp/VarpDec.java rename to plugin/src/main/java/ru/abstractmenus/commands/varp/VarpDec.java diff --git a/src/main/java/ru/abstractmenus/commands/varp/VarpDiv.java b/plugin/src/main/java/ru/abstractmenus/commands/varp/VarpDiv.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/varp/VarpDiv.java rename to plugin/src/main/java/ru/abstractmenus/commands/varp/VarpDiv.java diff --git a/src/main/java/ru/abstractmenus/commands/varp/VarpGet.java b/plugin/src/main/java/ru/abstractmenus/commands/varp/VarpGet.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/varp/VarpGet.java rename to plugin/src/main/java/ru/abstractmenus/commands/varp/VarpGet.java diff --git a/src/main/java/ru/abstractmenus/commands/varp/VarpInc.java b/plugin/src/main/java/ru/abstractmenus/commands/varp/VarpInc.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/varp/VarpInc.java rename to plugin/src/main/java/ru/abstractmenus/commands/varp/VarpInc.java diff --git a/src/main/java/ru/abstractmenus/commands/varp/VarpMul.java b/plugin/src/main/java/ru/abstractmenus/commands/varp/VarpMul.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/varp/VarpMul.java rename to plugin/src/main/java/ru/abstractmenus/commands/varp/VarpMul.java diff --git a/src/main/java/ru/abstractmenus/commands/varp/VarpRem.java b/plugin/src/main/java/ru/abstractmenus/commands/varp/VarpRem.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/varp/VarpRem.java rename to plugin/src/main/java/ru/abstractmenus/commands/varp/VarpRem.java diff --git a/src/main/java/ru/abstractmenus/commands/varp/VarpSet.java b/plugin/src/main/java/ru/abstractmenus/commands/varp/VarpSet.java similarity index 100% rename from src/main/java/ru/abstractmenus/commands/varp/VarpSet.java rename to plugin/src/main/java/ru/abstractmenus/commands/varp/VarpSet.java diff --git a/src/main/java/ru/abstractmenus/data/Actions.java b/plugin/src/main/java/ru/abstractmenus/data/Actions.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/Actions.java rename to plugin/src/main/java/ru/abstractmenus/data/Actions.java diff --git a/src/main/java/ru/abstractmenus/data/BannerData.java b/plugin/src/main/java/ru/abstractmenus/data/BannerData.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/BannerData.java rename to plugin/src/main/java/ru/abstractmenus/data/BannerData.java diff --git a/src/main/java/ru/abstractmenus/data/BookData.java b/plugin/src/main/java/ru/abstractmenus/data/BookData.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/BookData.java rename to plugin/src/main/java/ru/abstractmenus/data/BookData.java diff --git a/src/main/java/ru/abstractmenus/data/EntityData.java b/plugin/src/main/java/ru/abstractmenus/data/EntityData.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/EntityData.java rename to plugin/src/main/java/ru/abstractmenus/data/EntityData.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionBookOpen.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionBookOpen.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionBookOpen.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionBookOpen.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionBroadcast.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionBroadcast.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionBroadcast.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionBroadcast.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionBungeeConnect.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionBungeeConnect.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionBungeeConnect.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionBungeeConnect.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionButtonRemove.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionButtonRemove.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionButtonRemove.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionButtonRemove.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionButtonSet.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionButtonSet.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionButtonSet.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionButtonSet.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionCommand.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionCommand.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionCommand.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionCommand.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionFoodLevelSet.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionFoodLevelSet.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionFoodLevelSet.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionFoodLevelSet.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionGameModeSet.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionGameModeSet.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionGameModeSet.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionGameModeSet.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionGroupAdd.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionGroupAdd.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionGroupAdd.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionGroupAdd.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionGroupRemove.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionGroupRemove.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionGroupRemove.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionGroupRemove.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionHealthSet.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionHealthSet.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionHealthSet.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionHealthSet.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionInputChat.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionInputChat.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionInputChat.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionInputChat.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionInventoryClear.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionInventoryClear.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionInventoryClear.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionInventoryClear.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionItemAdd.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionItemAdd.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionItemAdd.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionItemAdd.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionItemClear.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionItemClear.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionItemClear.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionItemClear.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionItemRefresh.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionItemRefresh.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionItemRefresh.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionItemRefresh.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionItemRemove.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionItemRemove.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionItemRemove.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionItemRemove.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionLevelGive.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionLevelGive.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionLevelGive.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionLevelGive.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionLevelTake.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionLevelTake.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionLevelTake.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionLevelTake.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionLog.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionLog.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionLog.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionLog.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionLuckPermsMetaRemove.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionLuckPermsMetaRemove.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionLuckPermsMetaRemove.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionLuckPermsMetaRemove.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionLuckPermsMetaSet.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionLuckPermsMetaSet.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionLuckPermsMetaSet.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionLuckPermsMetaSet.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionMenuClose.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMenuClose.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionMenuClose.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionMenuClose.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionMenuOpen.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMenuOpen.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionMenuOpen.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionMenuOpen.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionMenuOpenCtx.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMenuOpenCtx.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionMenuOpenCtx.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionMenuOpenCtx.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionMenuRefresh.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMenuRefresh.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionMenuRefresh.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionMenuRefresh.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionMessage.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMessage.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionMessage.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionMessage.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionMiniMessage.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMiniMessage.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionMiniMessage.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionMiniMessage.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionMoneyGive.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMoneyGive.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionMoneyGive.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionMoneyGive.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionMoneyTake.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMoneyTake.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionMoneyTake.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionMoneyTake.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionPageNext.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPageNext.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionPageNext.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionPageNext.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionPagePrev.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPagePrev.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionPagePrev.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionPagePrev.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionPermissionGive.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPermissionGive.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionPermissionGive.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionPermissionGive.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionPermissionRemove.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPermissionRemove.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionPermissionRemove.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionPermissionRemove.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionPlaceItem.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPlaceItem.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionPlaceItem.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionPlaceItem.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionPlacedItemRemove.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPlacedItemRemove.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionPlacedItemRemove.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionPlacedItemRemove.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionPlayerChat.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPlayerChat.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionPlayerChat.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionPlayerChat.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionPotionEffect.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPotionEffect.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionPotionEffect.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionPotionEffect.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionPotionEffectRemove.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPotionEffectRemove.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionPotionEffectRemove.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionPotionEffectRemove.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionPropertyRemove.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPropertyRemove.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionPropertyRemove.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionPropertyRemove.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionPropertySet.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPropertySet.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionPropertySet.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionPropertySet.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionRecipeAdd.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionRecipeAdd.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionRecipeAdd.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionRecipeAdd.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionSkinReset.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionSkinReset.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionSkinReset.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionSkinReset.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionSkinSet.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionSkinSet.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionSkinSet.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionSkinSet.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionSound.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionSound.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionSound.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionSound.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionSoundCustom.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionSoundCustom.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionSoundCustom.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionSoundCustom.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionTeleport.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionTeleport.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionTeleport.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionTeleport.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionXpGive.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionXpGive.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionXpGive.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionXpGive.java diff --git a/src/main/java/ru/abstractmenus/data/actions/ActionXpTake.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionXpTake.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/ActionXpTake.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/ActionXpTake.java diff --git a/src/main/java/ru/abstractmenus/data/actions/MenuActions.java b/plugin/src/main/java/ru/abstractmenus/data/actions/MenuActions.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/MenuActions.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/MenuActions.java diff --git a/src/main/java/ru/abstractmenus/data/actions/var/ActionVarDec.java b/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarDec.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/var/ActionVarDec.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarDec.java diff --git a/src/main/java/ru/abstractmenus/data/actions/var/ActionVarDiv.java b/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarDiv.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/var/ActionVarDiv.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarDiv.java diff --git a/src/main/java/ru/abstractmenus/data/actions/var/ActionVarInc.java b/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarInc.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/var/ActionVarInc.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarInc.java diff --git a/src/main/java/ru/abstractmenus/data/actions/var/ActionVarMul.java b/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarMul.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/var/ActionVarMul.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarMul.java diff --git a/src/main/java/ru/abstractmenus/data/actions/var/ActionVarRem.java b/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarRem.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/var/ActionVarRem.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarRem.java diff --git a/src/main/java/ru/abstractmenus/data/actions/var/ActionVarSet.java b/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarSet.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/var/ActionVarSet.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarSet.java diff --git a/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpDec.java b/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpDec.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpDec.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpDec.java diff --git a/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpDiv.java b/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpDiv.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpDiv.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpDiv.java diff --git a/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpInc.java b/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpInc.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpInc.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpInc.java diff --git a/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpMul.java b/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpMul.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpMul.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpMul.java diff --git a/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpRem.java b/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpRem.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpRem.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpRem.java diff --git a/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpSet.java b/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpSet.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpSet.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpSet.java diff --git a/src/main/java/ru/abstractmenus/data/actions/wrappers/ActionBulk.java b/plugin/src/main/java/ru/abstractmenus/data/actions/wrappers/ActionBulk.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/wrappers/ActionBulk.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/wrappers/ActionBulk.java diff --git a/src/main/java/ru/abstractmenus/data/actions/wrappers/ActionDelay.java b/plugin/src/main/java/ru/abstractmenus/data/actions/wrappers/ActionDelay.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/wrappers/ActionDelay.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/wrappers/ActionDelay.java diff --git a/src/main/java/ru/abstractmenus/data/actions/wrappers/ActionPlayerScope.java b/plugin/src/main/java/ru/abstractmenus/data/actions/wrappers/ActionPlayerScope.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/wrappers/ActionPlayerScope.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/wrappers/ActionPlayerScope.java diff --git a/src/main/java/ru/abstractmenus/data/actions/wrappers/ActionRandomActions.java b/plugin/src/main/java/ru/abstractmenus/data/actions/wrappers/ActionRandomActions.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/actions/wrappers/ActionRandomActions.java rename to plugin/src/main/java/ru/abstractmenus/data/actions/wrappers/ActionRandomActions.java diff --git a/src/main/java/ru/abstractmenus/data/activators/ActivatorUtil.java b/plugin/src/main/java/ru/abstractmenus/data/activators/ActivatorUtil.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/activators/ActivatorUtil.java rename to plugin/src/main/java/ru/abstractmenus/data/activators/ActivatorUtil.java diff --git a/src/main/java/ru/abstractmenus/data/activators/Activators.java b/plugin/src/main/java/ru/abstractmenus/data/activators/Activators.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/activators/Activators.java rename to plugin/src/main/java/ru/abstractmenus/data/activators/Activators.java diff --git a/src/main/java/ru/abstractmenus/data/activators/OpenButton.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenButton.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/activators/OpenButton.java rename to plugin/src/main/java/ru/abstractmenus/data/activators/OpenButton.java diff --git a/src/main/java/ru/abstractmenus/data/activators/OpenChat.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenChat.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/activators/OpenChat.java rename to plugin/src/main/java/ru/abstractmenus/data/activators/OpenChat.java diff --git a/src/main/java/ru/abstractmenus/data/activators/OpenChatContains.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenChatContains.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/activators/OpenChatContains.java rename to plugin/src/main/java/ru/abstractmenus/data/activators/OpenChatContains.java diff --git a/src/main/java/ru/abstractmenus/data/activators/OpenClickBlock.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenClickBlock.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/activators/OpenClickBlock.java rename to plugin/src/main/java/ru/abstractmenus/data/activators/OpenClickBlock.java diff --git a/src/main/java/ru/abstractmenus/data/activators/OpenClickBlockType.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenClickBlockType.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/activators/OpenClickBlockType.java rename to plugin/src/main/java/ru/abstractmenus/data/activators/OpenClickBlockType.java diff --git a/src/main/java/ru/abstractmenus/data/activators/OpenClickEntity.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenClickEntity.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/activators/OpenClickEntity.java rename to plugin/src/main/java/ru/abstractmenus/data/activators/OpenClickEntity.java diff --git a/src/main/java/ru/abstractmenus/data/activators/OpenClickItem.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenClickItem.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/activators/OpenClickItem.java rename to plugin/src/main/java/ru/abstractmenus/data/activators/OpenClickItem.java diff --git a/src/main/java/ru/abstractmenus/data/activators/OpenClickNPC.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenClickNPC.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/activators/OpenClickNPC.java rename to plugin/src/main/java/ru/abstractmenus/data/activators/OpenClickNPC.java diff --git a/src/main/java/ru/abstractmenus/data/activators/OpenCommand.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenCommand.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/activators/OpenCommand.java rename to plugin/src/main/java/ru/abstractmenus/data/activators/OpenCommand.java diff --git a/src/main/java/ru/abstractmenus/data/activators/OpenJoin.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenJoin.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/activators/OpenJoin.java rename to plugin/src/main/java/ru/abstractmenus/data/activators/OpenJoin.java diff --git a/src/main/java/ru/abstractmenus/data/activators/OpenLever.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenLever.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/activators/OpenLever.java rename to plugin/src/main/java/ru/abstractmenus/data/activators/OpenLever.java diff --git a/src/main/java/ru/abstractmenus/data/activators/OpenPlate.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenPlate.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/activators/OpenPlate.java rename to plugin/src/main/java/ru/abstractmenus/data/activators/OpenPlate.java diff --git a/src/main/java/ru/abstractmenus/data/activators/OpenRegionEnter.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenRegionEnter.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/activators/OpenRegionEnter.java rename to plugin/src/main/java/ru/abstractmenus/data/activators/OpenRegionEnter.java diff --git a/src/main/java/ru/abstractmenus/data/activators/OpenRegionLeave.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenRegionLeave.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/activators/OpenRegionLeave.java rename to plugin/src/main/java/ru/abstractmenus/data/activators/OpenRegionLeave.java diff --git a/src/main/java/ru/abstractmenus/data/activators/OpenShiftClickEntity.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenShiftClickEntity.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/activators/OpenShiftClickEntity.java rename to plugin/src/main/java/ru/abstractmenus/data/activators/OpenShiftClickEntity.java diff --git a/src/main/java/ru/abstractmenus/data/activators/OpenSign.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenSign.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/activators/OpenSign.java rename to plugin/src/main/java/ru/abstractmenus/data/activators/OpenSign.java diff --git a/src/main/java/ru/abstractmenus/data/activators/OpenSwapItems.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenSwapItems.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/activators/OpenSwapItems.java rename to plugin/src/main/java/ru/abstractmenus/data/activators/OpenSwapItems.java diff --git a/src/main/java/ru/abstractmenus/data/catalogs/Catalogs.java b/plugin/src/main/java/ru/abstractmenus/data/catalogs/Catalogs.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/catalogs/Catalogs.java rename to plugin/src/main/java/ru/abstractmenus/data/catalogs/Catalogs.java diff --git a/src/main/java/ru/abstractmenus/data/catalogs/EntityCatalog.java b/plugin/src/main/java/ru/abstractmenus/data/catalogs/EntityCatalog.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/catalogs/EntityCatalog.java rename to plugin/src/main/java/ru/abstractmenus/data/catalogs/EntityCatalog.java diff --git a/src/main/java/ru/abstractmenus/data/catalogs/IteratorCatalog.java b/plugin/src/main/java/ru/abstractmenus/data/catalogs/IteratorCatalog.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/catalogs/IteratorCatalog.java rename to plugin/src/main/java/ru/abstractmenus/data/catalogs/IteratorCatalog.java diff --git a/src/main/java/ru/abstractmenus/data/catalogs/PlayerCatalog.java b/plugin/src/main/java/ru/abstractmenus/data/catalogs/PlayerCatalog.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/catalogs/PlayerCatalog.java rename to plugin/src/main/java/ru/abstractmenus/data/catalogs/PlayerCatalog.java diff --git a/src/main/java/ru/abstractmenus/data/catalogs/ServerCatalog.java b/plugin/src/main/java/ru/abstractmenus/data/catalogs/ServerCatalog.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/catalogs/ServerCatalog.java rename to plugin/src/main/java/ru/abstractmenus/data/catalogs/ServerCatalog.java diff --git a/src/main/java/ru/abstractmenus/data/catalogs/SliceCatalog.java b/plugin/src/main/java/ru/abstractmenus/data/catalogs/SliceCatalog.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/catalogs/SliceCatalog.java rename to plugin/src/main/java/ru/abstractmenus/data/catalogs/SliceCatalog.java diff --git a/src/main/java/ru/abstractmenus/data/catalogs/WorldCatalog.java b/plugin/src/main/java/ru/abstractmenus/data/catalogs/WorldCatalog.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/catalogs/WorldCatalog.java rename to plugin/src/main/java/ru/abstractmenus/data/catalogs/WorldCatalog.java diff --git a/src/main/java/ru/abstractmenus/data/comparator/BooleanEvaluator.java b/plugin/src/main/java/ru/abstractmenus/data/comparator/BooleanEvaluator.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/comparator/BooleanEvaluator.java rename to plugin/src/main/java/ru/abstractmenus/data/comparator/BooleanEvaluator.java diff --git a/src/main/java/ru/abstractmenus/data/comparator/LegacyValueComparator.java b/plugin/src/main/java/ru/abstractmenus/data/comparator/LegacyValueComparator.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/comparator/LegacyValueComparator.java rename to plugin/src/main/java/ru/abstractmenus/data/comparator/LegacyValueComparator.java diff --git a/src/main/java/ru/abstractmenus/data/comparator/ModernValueComparator.java b/plugin/src/main/java/ru/abstractmenus/data/comparator/ModernValueComparator.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/comparator/ModernValueComparator.java rename to plugin/src/main/java/ru/abstractmenus/data/comparator/ModernValueComparator.java diff --git a/src/main/java/ru/abstractmenus/data/comparator/ValueComparator.java b/plugin/src/main/java/ru/abstractmenus/data/comparator/ValueComparator.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/comparator/ValueComparator.java rename to plugin/src/main/java/ru/abstractmenus/data/comparator/ValueComparator.java diff --git a/src/main/java/ru/abstractmenus/data/properties/ItemProps.java b/plugin/src/main/java/ru/abstractmenus/data/properties/ItemProps.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/ItemProps.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/ItemProps.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropAttributeModifier.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropAttributeModifier.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropAttributeModifier.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropAttributeModifier.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropBannerData.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropBannerData.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropBannerData.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropBannerData.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropBindings.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropBindings.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropBindings.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropBindings.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropBookData.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropBookData.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropBookData.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropBookData.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropColor.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropColor.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropColor.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropColor.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropCount.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropCount.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropCount.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropCount.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropDamage.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropDamage.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropDamage.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropDamage.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropData.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropData.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropData.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropData.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropEnchantStore.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropEnchantStore.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropEnchantStore.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropEnchantStore.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropEnchantments.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropEnchantments.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropEnchantments.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropEnchantments.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropEquipItem.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropEquipItem.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropEquipItem.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropEquipItem.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropFireworkData.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropFireworkData.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropFireworkData.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropFireworkData.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropFlags.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropFlags.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropFlags.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropFlags.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropGlow.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropGlow.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropGlow.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropGlow.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropHDB.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropHDB.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropHDB.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropHDB.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropItemsAdder.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropItemsAdder.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropItemsAdder.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropItemsAdder.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropKnowledgeBook.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropKnowledgeBook.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropKnowledgeBook.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropKnowledgeBook.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropLPMeta.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropLPMeta.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropLPMeta.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropLPMeta.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropLore.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropLore.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropLore.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropLore.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropLoreLight.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropLoreLight.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropLoreLight.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropLoreLight.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropMaterial.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropMaterial.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropMaterial.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropMaterial.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropMmoItem.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropMmoItem.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropMmoItem.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropMmoItem.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropModel.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropModel.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropModel.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropModel.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropName.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropName.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropName.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropName.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropNameLight.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropNameLight.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropNameLight.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropNameLight.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropNbt.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropNbt.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropNbt.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropNbt.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropOraxen.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropOraxen.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropOraxen.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropOraxen.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropPotionData.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropPotionData.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropPotionData.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropPotionData.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropSerialized.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropSerialized.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropSerialized.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropSerialized.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropShieldData.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropShieldData.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropShieldData.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropShieldData.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropSkullOwner.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropSkullOwner.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropSkullOwner.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropSkullOwner.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropTexture.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropTexture.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropTexture.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropTexture.java diff --git a/src/main/java/ru/abstractmenus/data/properties/PropUnbreakable.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropUnbreakable.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/properties/PropUnbreakable.java rename to plugin/src/main/java/ru/abstractmenus/data/properties/PropUnbreakable.java diff --git a/src/main/java/ru/abstractmenus/data/rules/MenuRules.java b/plugin/src/main/java/ru/abstractmenus/data/rules/MenuRules.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/MenuRules.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/MenuRules.java diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleBungeeIsOnline.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleBungeeIsOnline.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/RuleBungeeIsOnline.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleBungeeIsOnline.java diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleBungeeOnline.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleBungeeOnline.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/RuleBungeeOnline.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleBungeeOnline.java diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleChance.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleChance.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/RuleChance.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleChance.java diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleExistVar.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleExistVar.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/RuleExistVar.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleExistVar.java diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleExistVarp.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleExistVarp.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/RuleExistVarp.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleExistVarp.java diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleFoodLevel.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleFoodLevel.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/RuleFoodLevel.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleFoodLevel.java diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleFreeSlot.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleFreeSlot.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/RuleFreeSlot.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleFreeSlot.java diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleFreeSlotCount.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleFreeSlotCount.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/RuleFreeSlotCount.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleFreeSlotCount.java diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleGameMode.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleGameMode.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/RuleGameMode.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleGameMode.java diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleGroup.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleGroup.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/RuleGroup.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleGroup.java diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleHealth.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleHealth.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/RuleHealth.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleHealth.java diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleHeldItem.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleHeldItem.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/RuleHeldItem.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleHeldItem.java diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleIf.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleIf.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/RuleIf.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleIf.java diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleInventoryItem.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleInventoryItem.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/RuleInventoryItem.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleInventoryItem.java diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleJS.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleJS.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/RuleJS.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleJS.java diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleLevel.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleLevel.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/RuleLevel.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleLevel.java diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleMoney.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleMoney.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/RuleMoney.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleMoney.java diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleOnline.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleOnline.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/RuleOnline.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleOnline.java diff --git a/src/main/java/ru/abstractmenus/data/rules/RulePermission.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RulePermission.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/RulePermission.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RulePermission.java diff --git a/src/main/java/ru/abstractmenus/data/rules/RulePlacedItem.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RulePlacedItem.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/RulePlacedItem.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RulePlacedItem.java diff --git a/src/main/java/ru/abstractmenus/data/rules/RulePlayerIsOnline.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RulePlayerIsOnline.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/RulePlayerIsOnline.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RulePlayerIsOnline.java diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleRegion.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleRegion.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/RuleRegion.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleRegion.java diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleWorld.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleWorld.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/RuleWorld.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleWorld.java diff --git a/src/main/java/ru/abstractmenus/data/rules/RuleXp.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleXp.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/RuleXp.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/RuleXp.java diff --git a/src/main/java/ru/abstractmenus/data/rules/logical/RuleAnd.java b/plugin/src/main/java/ru/abstractmenus/data/rules/logical/RuleAnd.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/logical/RuleAnd.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/logical/RuleAnd.java diff --git a/src/main/java/ru/abstractmenus/data/rules/logical/RuleNot.java b/plugin/src/main/java/ru/abstractmenus/data/rules/logical/RuleNot.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/logical/RuleNot.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/logical/RuleNot.java diff --git a/src/main/java/ru/abstractmenus/data/rules/logical/RuleOneOf.java b/plugin/src/main/java/ru/abstractmenus/data/rules/logical/RuleOneOf.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/logical/RuleOneOf.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/logical/RuleOneOf.java diff --git a/src/main/java/ru/abstractmenus/data/rules/logical/RuleOr.java b/plugin/src/main/java/ru/abstractmenus/data/rules/logical/RuleOr.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/logical/RuleOr.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/logical/RuleOr.java diff --git a/src/main/java/ru/abstractmenus/data/rules/logical/RulePlayerScope.java b/plugin/src/main/java/ru/abstractmenus/data/rules/logical/RulePlayerScope.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/logical/RulePlayerScope.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/logical/RulePlayerScope.java diff --git a/src/main/java/ru/abstractmenus/data/rules/logical/RulesGroup.java b/plugin/src/main/java/ru/abstractmenus/data/rules/logical/RulesGroup.java similarity index 100% rename from src/main/java/ru/abstractmenus/data/rules/logical/RulesGroup.java rename to plugin/src/main/java/ru/abstractmenus/data/rules/logical/RulesGroup.java diff --git a/src/main/java/ru/abstractmenus/datatype/DataType.java b/plugin/src/main/java/ru/abstractmenus/datatype/DataType.java similarity index 100% rename from src/main/java/ru/abstractmenus/datatype/DataType.java rename to plugin/src/main/java/ru/abstractmenus/datatype/DataType.java diff --git a/src/main/java/ru/abstractmenus/datatype/TypeBool.java b/plugin/src/main/java/ru/abstractmenus/datatype/TypeBool.java similarity index 100% rename from src/main/java/ru/abstractmenus/datatype/TypeBool.java rename to plugin/src/main/java/ru/abstractmenus/datatype/TypeBool.java diff --git a/src/main/java/ru/abstractmenus/datatype/TypeByte.java b/plugin/src/main/java/ru/abstractmenus/datatype/TypeByte.java similarity index 100% rename from src/main/java/ru/abstractmenus/datatype/TypeByte.java rename to plugin/src/main/java/ru/abstractmenus/datatype/TypeByte.java diff --git a/src/main/java/ru/abstractmenus/datatype/TypeColor.java b/plugin/src/main/java/ru/abstractmenus/datatype/TypeColor.java similarity index 100% rename from src/main/java/ru/abstractmenus/datatype/TypeColor.java rename to plugin/src/main/java/ru/abstractmenus/datatype/TypeColor.java diff --git a/src/main/java/ru/abstractmenus/datatype/TypeDouble.java b/plugin/src/main/java/ru/abstractmenus/datatype/TypeDouble.java similarity index 100% rename from src/main/java/ru/abstractmenus/datatype/TypeDouble.java rename to plugin/src/main/java/ru/abstractmenus/datatype/TypeDouble.java diff --git a/src/main/java/ru/abstractmenus/datatype/TypeEnum.java b/plugin/src/main/java/ru/abstractmenus/datatype/TypeEnum.java similarity index 100% rename from src/main/java/ru/abstractmenus/datatype/TypeEnum.java rename to plugin/src/main/java/ru/abstractmenus/datatype/TypeEnum.java diff --git a/src/main/java/ru/abstractmenus/datatype/TypeFloat.java b/plugin/src/main/java/ru/abstractmenus/datatype/TypeFloat.java similarity index 100% rename from src/main/java/ru/abstractmenus/datatype/TypeFloat.java rename to plugin/src/main/java/ru/abstractmenus/datatype/TypeFloat.java diff --git a/src/main/java/ru/abstractmenus/datatype/TypeInt.java b/plugin/src/main/java/ru/abstractmenus/datatype/TypeInt.java similarity index 100% rename from src/main/java/ru/abstractmenus/datatype/TypeInt.java rename to plugin/src/main/java/ru/abstractmenus/datatype/TypeInt.java diff --git a/src/main/java/ru/abstractmenus/datatype/TypeLocation.java b/plugin/src/main/java/ru/abstractmenus/datatype/TypeLocation.java similarity index 100% rename from src/main/java/ru/abstractmenus/datatype/TypeLocation.java rename to plugin/src/main/java/ru/abstractmenus/datatype/TypeLocation.java diff --git a/src/main/java/ru/abstractmenus/datatype/TypeLong.java b/plugin/src/main/java/ru/abstractmenus/datatype/TypeLong.java similarity index 100% rename from src/main/java/ru/abstractmenus/datatype/TypeLong.java rename to plugin/src/main/java/ru/abstractmenus/datatype/TypeLong.java diff --git a/src/main/java/ru/abstractmenus/datatype/TypeMaterial.java b/plugin/src/main/java/ru/abstractmenus/datatype/TypeMaterial.java similarity index 100% rename from src/main/java/ru/abstractmenus/datatype/TypeMaterial.java rename to plugin/src/main/java/ru/abstractmenus/datatype/TypeMaterial.java diff --git a/src/main/java/ru/abstractmenus/datatype/TypeShort.java b/plugin/src/main/java/ru/abstractmenus/datatype/TypeShort.java similarity index 100% rename from src/main/java/ru/abstractmenus/datatype/TypeShort.java rename to plugin/src/main/java/ru/abstractmenus/datatype/TypeShort.java diff --git a/src/main/java/ru/abstractmenus/datatype/TypeSlot.java b/plugin/src/main/java/ru/abstractmenus/datatype/TypeSlot.java similarity index 100% rename from src/main/java/ru/abstractmenus/datatype/TypeSlot.java rename to plugin/src/main/java/ru/abstractmenus/datatype/TypeSlot.java diff --git a/src/main/java/ru/abstractmenus/datatype/TypeSound.java b/plugin/src/main/java/ru/abstractmenus/datatype/TypeSound.java similarity index 100% rename from src/main/java/ru/abstractmenus/datatype/TypeSound.java rename to plugin/src/main/java/ru/abstractmenus/datatype/TypeSound.java diff --git a/src/main/java/ru/abstractmenus/datatype/TypeWorld.java b/plugin/src/main/java/ru/abstractmenus/datatype/TypeWorld.java similarity index 100% rename from src/main/java/ru/abstractmenus/datatype/TypeWorld.java rename to plugin/src/main/java/ru/abstractmenus/datatype/TypeWorld.java diff --git a/src/main/java/ru/abstractmenus/events/RegionEnterEvent.java b/plugin/src/main/java/ru/abstractmenus/events/RegionEnterEvent.java similarity index 100% rename from src/main/java/ru/abstractmenus/events/RegionEnterEvent.java rename to plugin/src/main/java/ru/abstractmenus/events/RegionEnterEvent.java diff --git a/src/main/java/ru/abstractmenus/events/RegionEvent.java b/plugin/src/main/java/ru/abstractmenus/events/RegionEvent.java similarity index 100% rename from src/main/java/ru/abstractmenus/events/RegionEvent.java rename to plugin/src/main/java/ru/abstractmenus/events/RegionEvent.java diff --git a/src/main/java/ru/abstractmenus/events/RegionLeaveEvent.java b/plugin/src/main/java/ru/abstractmenus/events/RegionLeaveEvent.java similarity index 100% rename from src/main/java/ru/abstractmenus/events/RegionLeaveEvent.java rename to plugin/src/main/java/ru/abstractmenus/events/RegionLeaveEvent.java diff --git a/src/main/java/ru/abstractmenus/extractors/BlockExtractor.java b/plugin/src/main/java/ru/abstractmenus/extractors/BlockExtractor.java similarity index 100% rename from src/main/java/ru/abstractmenus/extractors/BlockExtractor.java rename to plugin/src/main/java/ru/abstractmenus/extractors/BlockExtractor.java diff --git a/src/main/java/ru/abstractmenus/extractors/CommandExtractor.java b/plugin/src/main/java/ru/abstractmenus/extractors/CommandExtractor.java similarity index 100% rename from src/main/java/ru/abstractmenus/extractors/CommandExtractor.java rename to plugin/src/main/java/ru/abstractmenus/extractors/CommandExtractor.java diff --git a/src/main/java/ru/abstractmenus/extractors/EntityExtractor.java b/plugin/src/main/java/ru/abstractmenus/extractors/EntityExtractor.java similarity index 100% rename from src/main/java/ru/abstractmenus/extractors/EntityExtractor.java rename to plugin/src/main/java/ru/abstractmenus/extractors/EntityExtractor.java diff --git a/src/main/java/ru/abstractmenus/extractors/ItemStackExtractor.java b/plugin/src/main/java/ru/abstractmenus/extractors/ItemStackExtractor.java similarity index 100% rename from src/main/java/ru/abstractmenus/extractors/ItemStackExtractor.java rename to plugin/src/main/java/ru/abstractmenus/extractors/ItemStackExtractor.java diff --git a/src/main/java/ru/abstractmenus/extractors/NPCExtractor.java b/plugin/src/main/java/ru/abstractmenus/extractors/NPCExtractor.java similarity index 100% rename from src/main/java/ru/abstractmenus/extractors/NPCExtractor.java rename to plugin/src/main/java/ru/abstractmenus/extractors/NPCExtractor.java diff --git a/src/main/java/ru/abstractmenus/extractors/PlayerExtractor.java b/plugin/src/main/java/ru/abstractmenus/extractors/PlayerExtractor.java similarity index 100% rename from src/main/java/ru/abstractmenus/extractors/PlayerExtractor.java rename to plugin/src/main/java/ru/abstractmenus/extractors/PlayerExtractor.java diff --git a/src/main/java/ru/abstractmenus/extractors/RegionExtractor.java b/plugin/src/main/java/ru/abstractmenus/extractors/RegionExtractor.java similarity index 100% rename from src/main/java/ru/abstractmenus/extractors/RegionExtractor.java rename to plugin/src/main/java/ru/abstractmenus/extractors/RegionExtractor.java diff --git a/src/main/java/ru/abstractmenus/extractors/WorldExtractor.java b/plugin/src/main/java/ru/abstractmenus/extractors/WorldExtractor.java similarity index 100% rename from src/main/java/ru/abstractmenus/extractors/WorldExtractor.java rename to plugin/src/main/java/ru/abstractmenus/extractors/WorldExtractor.java diff --git a/src/main/java/ru/abstractmenus/handlers/EconomyVaultHandler.java b/plugin/src/main/java/ru/abstractmenus/handlers/EconomyVaultHandler.java similarity index 100% rename from src/main/java/ru/abstractmenus/handlers/EconomyVaultHandler.java rename to plugin/src/main/java/ru/abstractmenus/handlers/EconomyVaultHandler.java diff --git a/src/main/java/ru/abstractmenus/handlers/LevelDefaultHandler.java b/plugin/src/main/java/ru/abstractmenus/handlers/LevelDefaultHandler.java similarity index 100% rename from src/main/java/ru/abstractmenus/handlers/LevelDefaultHandler.java rename to plugin/src/main/java/ru/abstractmenus/handlers/LevelDefaultHandler.java diff --git a/src/main/java/ru/abstractmenus/handlers/LuckPermsHandler.java b/plugin/src/main/java/ru/abstractmenus/handlers/LuckPermsHandler.java similarity index 100% rename from src/main/java/ru/abstractmenus/handlers/LuckPermsHandler.java rename to plugin/src/main/java/ru/abstractmenus/handlers/LuckPermsHandler.java diff --git a/src/main/java/ru/abstractmenus/handlers/PermissionDefaultHandler.java b/plugin/src/main/java/ru/abstractmenus/handlers/PermissionDefaultHandler.java similarity index 100% rename from src/main/java/ru/abstractmenus/handlers/PermissionDefaultHandler.java rename to plugin/src/main/java/ru/abstractmenus/handlers/PermissionDefaultHandler.java diff --git a/src/main/java/ru/abstractmenus/handlers/SkinsRestorerHandler.java b/plugin/src/main/java/ru/abstractmenus/handlers/SkinsRestorerHandler.java similarity index 100% rename from src/main/java/ru/abstractmenus/handlers/SkinsRestorerHandler.java rename to plugin/src/main/java/ru/abstractmenus/handlers/SkinsRestorerHandler.java diff --git a/src/main/java/ru/abstractmenus/handlers/placeholder/PlaceholderCustomHandler.java b/plugin/src/main/java/ru/abstractmenus/handlers/placeholder/PlaceholderCustomHandler.java similarity index 100% rename from src/main/java/ru/abstractmenus/handlers/placeholder/PlaceholderCustomHandler.java rename to plugin/src/main/java/ru/abstractmenus/handlers/placeholder/PlaceholderCustomHandler.java diff --git a/src/main/java/ru/abstractmenus/handlers/placeholder/PlaceholderDefaultHandler.java b/plugin/src/main/java/ru/abstractmenus/handlers/placeholder/PlaceholderDefaultHandler.java similarity index 100% rename from src/main/java/ru/abstractmenus/handlers/placeholder/PlaceholderDefaultHandler.java rename to plugin/src/main/java/ru/abstractmenus/handlers/placeholder/PlaceholderDefaultHandler.java diff --git a/src/main/java/ru/abstractmenus/listeners/ChatListener.java b/plugin/src/main/java/ru/abstractmenus/listeners/ChatListener.java similarity index 100% rename from src/main/java/ru/abstractmenus/listeners/ChatListener.java rename to plugin/src/main/java/ru/abstractmenus/listeners/ChatListener.java diff --git a/src/main/java/ru/abstractmenus/listeners/InventoryListener.java b/plugin/src/main/java/ru/abstractmenus/listeners/InventoryListener.java similarity index 100% rename from src/main/java/ru/abstractmenus/listeners/InventoryListener.java rename to plugin/src/main/java/ru/abstractmenus/listeners/InventoryListener.java diff --git a/src/main/java/ru/abstractmenus/listeners/PlayerListener.java b/plugin/src/main/java/ru/abstractmenus/listeners/PlayerListener.java similarity index 100% rename from src/main/java/ru/abstractmenus/listeners/PlayerListener.java rename to plugin/src/main/java/ru/abstractmenus/listeners/PlayerListener.java diff --git a/src/main/java/ru/abstractmenus/listeners/wg/PlayerMoveListener.java b/plugin/src/main/java/ru/abstractmenus/listeners/wg/PlayerMoveListener.java similarity index 100% rename from src/main/java/ru/abstractmenus/listeners/wg/PlayerMoveListener.java rename to plugin/src/main/java/ru/abstractmenus/listeners/wg/PlayerMoveListener.java diff --git a/src/main/java/ru/abstractmenus/listeners/wg/WGHandlers.java b/plugin/src/main/java/ru/abstractmenus/listeners/wg/WGHandlers.java similarity index 100% rename from src/main/java/ru/abstractmenus/listeners/wg/WGHandlers.java rename to plugin/src/main/java/ru/abstractmenus/listeners/wg/WGHandlers.java diff --git a/src/main/java/ru/abstractmenus/listeners/wg/WgRegionHandler.java b/plugin/src/main/java/ru/abstractmenus/listeners/wg/WgRegionHandler.java similarity index 100% rename from src/main/java/ru/abstractmenus/listeners/wg/WgRegionHandler.java rename to plugin/src/main/java/ru/abstractmenus/listeners/wg/WgRegionHandler.java diff --git a/src/main/java/ru/abstractmenus/menu/AbstractMenu.java b/plugin/src/main/java/ru/abstractmenus/menu/AbstractMenu.java similarity index 100% rename from src/main/java/ru/abstractmenus/menu/AbstractMenu.java rename to plugin/src/main/java/ru/abstractmenus/menu/AbstractMenu.java diff --git a/src/main/java/ru/abstractmenus/menu/MenuListener.java b/plugin/src/main/java/ru/abstractmenus/menu/MenuListener.java similarity index 100% rename from src/main/java/ru/abstractmenus/menu/MenuListener.java rename to plugin/src/main/java/ru/abstractmenus/menu/MenuListener.java diff --git a/src/main/java/ru/abstractmenus/menu/SimpleMenu.java b/plugin/src/main/java/ru/abstractmenus/menu/SimpleMenu.java similarity index 100% rename from src/main/java/ru/abstractmenus/menu/SimpleMenu.java rename to plugin/src/main/java/ru/abstractmenus/menu/SimpleMenu.java diff --git a/src/main/java/ru/abstractmenus/menu/animated/AnimatedMenu.java b/plugin/src/main/java/ru/abstractmenus/menu/animated/AnimatedMenu.java similarity index 100% rename from src/main/java/ru/abstractmenus/menu/animated/AnimatedMenu.java rename to plugin/src/main/java/ru/abstractmenus/menu/animated/AnimatedMenu.java diff --git a/src/main/java/ru/abstractmenus/menu/animated/Frame.java b/plugin/src/main/java/ru/abstractmenus/menu/animated/Frame.java similarity index 100% rename from src/main/java/ru/abstractmenus/menu/animated/Frame.java rename to plugin/src/main/java/ru/abstractmenus/menu/animated/Frame.java diff --git a/src/main/java/ru/abstractmenus/menu/generated/GeneratedMenu.java b/plugin/src/main/java/ru/abstractmenus/menu/generated/GeneratedMenu.java similarity index 100% rename from src/main/java/ru/abstractmenus/menu/generated/GeneratedMenu.java rename to plugin/src/main/java/ru/abstractmenus/menu/generated/GeneratedMenu.java diff --git a/src/main/java/ru/abstractmenus/menu/generated/Matrix.java b/plugin/src/main/java/ru/abstractmenus/menu/generated/Matrix.java similarity index 100% rename from src/main/java/ru/abstractmenus/menu/generated/Matrix.java rename to plugin/src/main/java/ru/abstractmenus/menu/generated/Matrix.java diff --git a/src/main/java/ru/abstractmenus/menu/item/InventoryItem.java b/plugin/src/main/java/ru/abstractmenus/menu/item/InventoryItem.java similarity index 100% rename from src/main/java/ru/abstractmenus/menu/item/InventoryItem.java rename to plugin/src/main/java/ru/abstractmenus/menu/item/InventoryItem.java diff --git a/src/main/java/ru/abstractmenus/menu/item/MenuItem.java b/plugin/src/main/java/ru/abstractmenus/menu/item/MenuItem.java similarity index 100% rename from src/main/java/ru/abstractmenus/menu/item/MenuItem.java rename to plugin/src/main/java/ru/abstractmenus/menu/item/MenuItem.java diff --git a/src/main/java/ru/abstractmenus/menu/item/SimpleItem.java b/plugin/src/main/java/ru/abstractmenus/menu/item/SimpleItem.java similarity index 100% rename from src/main/java/ru/abstractmenus/menu/item/SimpleItem.java rename to plugin/src/main/java/ru/abstractmenus/menu/item/SimpleItem.java diff --git a/src/main/java/ru/abstractmenus/nms/actionbar/ActionBar.java b/plugin/src/main/java/ru/abstractmenus/nms/actionbar/ActionBar.java similarity index 100% rename from src/main/java/ru/abstractmenus/nms/actionbar/ActionBar.java rename to plugin/src/main/java/ru/abstractmenus/nms/actionbar/ActionBar.java diff --git a/src/main/java/ru/abstractmenus/nms/actionbar/ActionBar_1_9.java b/plugin/src/main/java/ru/abstractmenus/nms/actionbar/ActionBar_1_9.java similarity index 100% rename from src/main/java/ru/abstractmenus/nms/actionbar/ActionBar_1_9.java rename to plugin/src/main/java/ru/abstractmenus/nms/actionbar/ActionBar_1_9.java diff --git a/src/main/java/ru/abstractmenus/nms/actionbar/ActionBar_PaperMc.java b/plugin/src/main/java/ru/abstractmenus/nms/actionbar/ActionBar_PaperMc.java similarity index 100% rename from src/main/java/ru/abstractmenus/nms/actionbar/ActionBar_PaperMc.java rename to plugin/src/main/java/ru/abstractmenus/nms/actionbar/ActionBar_PaperMc.java diff --git a/src/main/java/ru/abstractmenus/nms/book/Book.java b/plugin/src/main/java/ru/abstractmenus/nms/book/Book.java similarity index 100% rename from src/main/java/ru/abstractmenus/nms/book/Book.java rename to plugin/src/main/java/ru/abstractmenus/nms/book/Book.java diff --git a/src/main/java/ru/abstractmenus/nms/book/Book_1_15.java b/plugin/src/main/java/ru/abstractmenus/nms/book/Book_1_15.java similarity index 100% rename from src/main/java/ru/abstractmenus/nms/book/Book_1_15.java rename to plugin/src/main/java/ru/abstractmenus/nms/book/Book_1_15.java diff --git a/src/main/java/ru/abstractmenus/nms/title/SenderModern.java b/plugin/src/main/java/ru/abstractmenus/nms/title/SenderModern.java similarity index 100% rename from src/main/java/ru/abstractmenus/nms/title/SenderModern.java rename to plugin/src/main/java/ru/abstractmenus/nms/title/SenderModern.java diff --git a/src/main/java/ru/abstractmenus/nms/title/Title.java b/plugin/src/main/java/ru/abstractmenus/nms/title/Title.java similarity index 100% rename from src/main/java/ru/abstractmenus/nms/title/Title.java rename to plugin/src/main/java/ru/abstractmenus/nms/title/Title.java diff --git a/src/main/java/ru/abstractmenus/nms/title/TitleSender.java b/plugin/src/main/java/ru/abstractmenus/nms/title/TitleSender.java similarity index 100% rename from src/main/java/ru/abstractmenus/nms/title/TitleSender.java rename to plugin/src/main/java/ru/abstractmenus/nms/title/TitleSender.java diff --git a/src/main/java/ru/abstractmenus/placeholders/PAPIPlaceholders.java b/plugin/src/main/java/ru/abstractmenus/placeholders/PAPIPlaceholders.java similarity index 100% rename from src/main/java/ru/abstractmenus/placeholders/PAPIPlaceholders.java rename to plugin/src/main/java/ru/abstractmenus/placeholders/PAPIPlaceholders.java diff --git a/src/main/java/ru/abstractmenus/placeholders/PlaceholderHook.java b/plugin/src/main/java/ru/abstractmenus/placeholders/PlaceholderHook.java similarity index 100% rename from src/main/java/ru/abstractmenus/placeholders/PlaceholderHook.java rename to plugin/src/main/java/ru/abstractmenus/placeholders/PlaceholderHook.java diff --git a/src/main/java/ru/abstractmenus/placeholders/hooks/ActivatorPlaceholders.java b/plugin/src/main/java/ru/abstractmenus/placeholders/hooks/ActivatorPlaceholders.java similarity index 100% rename from src/main/java/ru/abstractmenus/placeholders/hooks/ActivatorPlaceholders.java rename to plugin/src/main/java/ru/abstractmenus/placeholders/hooks/ActivatorPlaceholders.java diff --git a/src/main/java/ru/abstractmenus/placeholders/hooks/BungeePlaceholders.java b/plugin/src/main/java/ru/abstractmenus/placeholders/hooks/BungeePlaceholders.java similarity index 100% rename from src/main/java/ru/abstractmenus/placeholders/hooks/BungeePlaceholders.java rename to plugin/src/main/java/ru/abstractmenus/placeholders/hooks/BungeePlaceholders.java diff --git a/src/main/java/ru/abstractmenus/placeholders/hooks/CatalogPlaceholders.java b/plugin/src/main/java/ru/abstractmenus/placeholders/hooks/CatalogPlaceholders.java similarity index 100% rename from src/main/java/ru/abstractmenus/placeholders/hooks/CatalogPlaceholders.java rename to plugin/src/main/java/ru/abstractmenus/placeholders/hooks/CatalogPlaceholders.java diff --git a/src/main/java/ru/abstractmenus/placeholders/hooks/HeadAnimPlaceholders.java b/plugin/src/main/java/ru/abstractmenus/placeholders/hooks/HeadAnimPlaceholders.java similarity index 100% rename from src/main/java/ru/abstractmenus/placeholders/hooks/HeadAnimPlaceholders.java rename to plugin/src/main/java/ru/abstractmenus/placeholders/hooks/HeadAnimPlaceholders.java diff --git a/src/main/java/ru/abstractmenus/placeholders/hooks/PlayerPlaceholders.java b/plugin/src/main/java/ru/abstractmenus/placeholders/hooks/PlayerPlaceholders.java similarity index 100% rename from src/main/java/ru/abstractmenus/placeholders/hooks/PlayerPlaceholders.java rename to plugin/src/main/java/ru/abstractmenus/placeholders/hooks/PlayerPlaceholders.java diff --git a/src/main/java/ru/abstractmenus/placeholders/hooks/ServerPlaceholders.java b/plugin/src/main/java/ru/abstractmenus/placeholders/hooks/ServerPlaceholders.java similarity index 100% rename from src/main/java/ru/abstractmenus/placeholders/hooks/ServerPlaceholders.java rename to plugin/src/main/java/ru/abstractmenus/placeholders/hooks/ServerPlaceholders.java diff --git a/src/main/java/ru/abstractmenus/placeholders/hooks/VarPlaceholders.java b/plugin/src/main/java/ru/abstractmenus/placeholders/hooks/VarPlaceholders.java similarity index 100% rename from src/main/java/ru/abstractmenus/placeholders/hooks/VarPlaceholders.java rename to plugin/src/main/java/ru/abstractmenus/placeholders/hooks/VarPlaceholders.java diff --git a/src/main/java/ru/abstractmenus/placeholders/hooks/dnd/ChangedItemPlaceholders.java b/plugin/src/main/java/ru/abstractmenus/placeholders/hooks/dnd/ChangedItemPlaceholders.java similarity index 100% rename from src/main/java/ru/abstractmenus/placeholders/hooks/dnd/ChangedItemPlaceholders.java rename to plugin/src/main/java/ru/abstractmenus/placeholders/hooks/dnd/ChangedItemPlaceholders.java diff --git a/src/main/java/ru/abstractmenus/placeholders/hooks/dnd/PlacedItemPlaceholders.java b/plugin/src/main/java/ru/abstractmenus/placeholders/hooks/dnd/PlacedItemPlaceholders.java similarity index 100% rename from src/main/java/ru/abstractmenus/placeholders/hooks/dnd/PlacedItemPlaceholders.java rename to plugin/src/main/java/ru/abstractmenus/placeholders/hooks/dnd/PlacedItemPlaceholders.java diff --git a/src/main/java/ru/abstractmenus/placeholders/hooks/dnd/TakenItemPlaceholders.java b/plugin/src/main/java/ru/abstractmenus/placeholders/hooks/dnd/TakenItemPlaceholders.java similarity index 100% rename from src/main/java/ru/abstractmenus/placeholders/hooks/dnd/TakenItemPlaceholders.java rename to plugin/src/main/java/ru/abstractmenus/placeholders/hooks/dnd/TakenItemPlaceholders.java diff --git a/src/main/java/ru/abstractmenus/serializers/BannerPatternSerializer.java b/plugin/src/main/java/ru/abstractmenus/serializers/BannerPatternSerializer.java similarity index 100% rename from src/main/java/ru/abstractmenus/serializers/BannerPatternSerializer.java rename to plugin/src/main/java/ru/abstractmenus/serializers/BannerPatternSerializer.java diff --git a/src/main/java/ru/abstractmenus/serializers/ColorSerializer.java b/plugin/src/main/java/ru/abstractmenus/serializers/ColorSerializer.java similarity index 100% rename from src/main/java/ru/abstractmenus/serializers/ColorSerializer.java rename to plugin/src/main/java/ru/abstractmenus/serializers/ColorSerializer.java diff --git a/src/main/java/ru/abstractmenus/serializers/EntityTypeSerializer.java b/plugin/src/main/java/ru/abstractmenus/serializers/EntityTypeSerializer.java similarity index 100% rename from src/main/java/ru/abstractmenus/serializers/EntityTypeSerializer.java rename to plugin/src/main/java/ru/abstractmenus/serializers/EntityTypeSerializer.java diff --git a/src/main/java/ru/abstractmenus/serializers/FireworkEffectSerializer.java b/plugin/src/main/java/ru/abstractmenus/serializers/FireworkEffectSerializer.java similarity index 100% rename from src/main/java/ru/abstractmenus/serializers/FireworkEffectSerializer.java rename to plugin/src/main/java/ru/abstractmenus/serializers/FireworkEffectSerializer.java diff --git a/src/main/java/ru/abstractmenus/serializers/ItemSerializer.java b/plugin/src/main/java/ru/abstractmenus/serializers/ItemSerializer.java similarity index 100% rename from src/main/java/ru/abstractmenus/serializers/ItemSerializer.java rename to plugin/src/main/java/ru/abstractmenus/serializers/ItemSerializer.java diff --git a/src/main/java/ru/abstractmenus/serializers/JsonSerializer.java b/plugin/src/main/java/ru/abstractmenus/serializers/JsonSerializer.java similarity index 100% rename from src/main/java/ru/abstractmenus/serializers/JsonSerializer.java rename to plugin/src/main/java/ru/abstractmenus/serializers/JsonSerializer.java diff --git a/src/main/java/ru/abstractmenus/serializers/LocationSerializer.java b/plugin/src/main/java/ru/abstractmenus/serializers/LocationSerializer.java similarity index 100% rename from src/main/java/ru/abstractmenus/serializers/LocationSerializer.java rename to plugin/src/main/java/ru/abstractmenus/serializers/LocationSerializer.java diff --git a/src/main/java/ru/abstractmenus/serializers/NbtCompoundSerializer.java b/plugin/src/main/java/ru/abstractmenus/serializers/NbtCompoundSerializer.java similarity index 100% rename from src/main/java/ru/abstractmenus/serializers/NbtCompoundSerializer.java rename to plugin/src/main/java/ru/abstractmenus/serializers/NbtCompoundSerializer.java diff --git a/src/main/java/ru/abstractmenus/serializers/PotionEffectSerializer.java b/plugin/src/main/java/ru/abstractmenus/serializers/PotionEffectSerializer.java similarity index 100% rename from src/main/java/ru/abstractmenus/serializers/PotionEffectSerializer.java rename to plugin/src/main/java/ru/abstractmenus/serializers/PotionEffectSerializer.java diff --git a/src/main/java/ru/abstractmenus/serializers/Serializers.java b/plugin/src/main/java/ru/abstractmenus/serializers/Serializers.java similarity index 100% rename from src/main/java/ru/abstractmenus/serializers/Serializers.java rename to plugin/src/main/java/ru/abstractmenus/serializers/Serializers.java diff --git a/src/main/java/ru/abstractmenus/serializers/ShapedRecipeSerializer.java b/plugin/src/main/java/ru/abstractmenus/serializers/ShapedRecipeSerializer.java similarity index 100% rename from src/main/java/ru/abstractmenus/serializers/ShapedRecipeSerializer.java rename to plugin/src/main/java/ru/abstractmenus/serializers/ShapedRecipeSerializer.java diff --git a/src/main/java/ru/abstractmenus/serializers/WorldSerializer.java b/plugin/src/main/java/ru/abstractmenus/serializers/WorldSerializer.java similarity index 100% rename from src/main/java/ru/abstractmenus/serializers/WorldSerializer.java rename to plugin/src/main/java/ru/abstractmenus/serializers/WorldSerializer.java diff --git a/src/main/java/ru/abstractmenus/serializers/menu/AnimatedMenuSerializer.java b/plugin/src/main/java/ru/abstractmenus/serializers/menu/AnimatedMenuSerializer.java similarity index 100% rename from src/main/java/ru/abstractmenus/serializers/menu/AnimatedMenuSerializer.java rename to plugin/src/main/java/ru/abstractmenus/serializers/menu/AnimatedMenuSerializer.java diff --git a/src/main/java/ru/abstractmenus/serializers/menu/BaseMenuSerializer.java b/plugin/src/main/java/ru/abstractmenus/serializers/menu/BaseMenuSerializer.java similarity index 100% rename from src/main/java/ru/abstractmenus/serializers/menu/BaseMenuSerializer.java rename to plugin/src/main/java/ru/abstractmenus/serializers/menu/BaseMenuSerializer.java diff --git a/src/main/java/ru/abstractmenus/serializers/menu/GeneratedMenuSerializer.java b/plugin/src/main/java/ru/abstractmenus/serializers/menu/GeneratedMenuSerializer.java similarity index 100% rename from src/main/java/ru/abstractmenus/serializers/menu/GeneratedMenuSerializer.java rename to plugin/src/main/java/ru/abstractmenus/serializers/menu/GeneratedMenuSerializer.java diff --git a/src/main/java/ru/abstractmenus/serializers/menu/MenuSerializer.java b/plugin/src/main/java/ru/abstractmenus/serializers/menu/MenuSerializer.java similarity index 100% rename from src/main/java/ru/abstractmenus/serializers/menu/MenuSerializer.java rename to plugin/src/main/java/ru/abstractmenus/serializers/menu/MenuSerializer.java diff --git a/src/main/java/ru/abstractmenus/serializers/menu/SimpleMenuSerializer.java b/plugin/src/main/java/ru/abstractmenus/serializers/menu/SimpleMenuSerializer.java similarity index 100% rename from src/main/java/ru/abstractmenus/serializers/menu/SimpleMenuSerializer.java rename to plugin/src/main/java/ru/abstractmenus/serializers/menu/SimpleMenuSerializer.java diff --git a/src/main/java/ru/abstractmenus/services/BungeeManager.java b/plugin/src/main/java/ru/abstractmenus/services/BungeeManager.java similarity index 100% rename from src/main/java/ru/abstractmenus/services/BungeeManager.java rename to plugin/src/main/java/ru/abstractmenus/services/BungeeManager.java diff --git a/src/main/java/ru/abstractmenus/services/HeadAnimManager.java b/plugin/src/main/java/ru/abstractmenus/services/HeadAnimManager.java similarity index 100% rename from src/main/java/ru/abstractmenus/services/HeadAnimManager.java rename to plugin/src/main/java/ru/abstractmenus/services/HeadAnimManager.java diff --git a/src/main/java/ru/abstractmenus/services/MenuManager.java b/plugin/src/main/java/ru/abstractmenus/services/MenuManager.java similarity index 100% rename from src/main/java/ru/abstractmenus/services/MenuManager.java rename to plugin/src/main/java/ru/abstractmenus/services/MenuManager.java diff --git a/src/main/java/ru/abstractmenus/services/ProfileStorage.java b/plugin/src/main/java/ru/abstractmenus/services/ProfileStorage.java similarity index 100% rename from src/main/java/ru/abstractmenus/services/ProfileStorage.java rename to plugin/src/main/java/ru/abstractmenus/services/ProfileStorage.java diff --git a/src/main/java/ru/abstractmenus/services/proxy/dto/ChangeSkinDTO.java b/plugin/src/main/java/ru/abstractmenus/services/proxy/dto/ChangeSkinDTO.java similarity index 100% rename from src/main/java/ru/abstractmenus/services/proxy/dto/ChangeSkinDTO.java rename to plugin/src/main/java/ru/abstractmenus/services/proxy/dto/ChangeSkinDTO.java diff --git a/src/main/java/ru/abstractmenus/util/ArrayListIterator.java b/plugin/src/main/java/ru/abstractmenus/util/ArrayListIterator.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/ArrayListIterator.java rename to plugin/src/main/java/ru/abstractmenus/util/ArrayListIterator.java diff --git a/src/main/java/ru/abstractmenus/util/FileUtils.java b/plugin/src/main/java/ru/abstractmenus/util/FileUtils.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/FileUtils.java rename to plugin/src/main/java/ru/abstractmenus/util/FileUtils.java diff --git a/src/main/java/ru/abstractmenus/util/LegacyColorTagReplacer.java b/plugin/src/main/java/ru/abstractmenus/util/LegacyColorTagReplacer.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/LegacyColorTagReplacer.java rename to plugin/src/main/java/ru/abstractmenus/util/LegacyColorTagReplacer.java diff --git a/src/main/java/ru/abstractmenus/util/MiniMessageUtil.java b/plugin/src/main/java/ru/abstractmenus/util/MiniMessageUtil.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/MiniMessageUtil.java rename to plugin/src/main/java/ru/abstractmenus/util/MiniMessageUtil.java diff --git a/src/main/java/ru/abstractmenus/util/NMS.java b/plugin/src/main/java/ru/abstractmenus/util/NMS.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/NMS.java rename to plugin/src/main/java/ru/abstractmenus/util/NMS.java diff --git a/src/main/java/ru/abstractmenus/util/NumberUtil.java b/plugin/src/main/java/ru/abstractmenus/util/NumberUtil.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/NumberUtil.java rename to plugin/src/main/java/ru/abstractmenus/util/NumberUtil.java diff --git a/src/main/java/ru/abstractmenus/util/RegionUtils.java b/plugin/src/main/java/ru/abstractmenus/util/RegionUtils.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/RegionUtils.java rename to plugin/src/main/java/ru/abstractmenus/util/RegionUtils.java diff --git a/src/main/java/ru/abstractmenus/util/SlotUtil.java b/plugin/src/main/java/ru/abstractmenus/util/SlotUtil.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/SlotUtil.java rename to plugin/src/main/java/ru/abstractmenus/util/SlotUtil.java diff --git a/src/main/java/ru/abstractmenus/util/StringUtil.java b/plugin/src/main/java/ru/abstractmenus/util/StringUtil.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/StringUtil.java rename to plugin/src/main/java/ru/abstractmenus/util/StringUtil.java diff --git a/src/main/java/ru/abstractmenus/util/TimeParser.java b/plugin/src/main/java/ru/abstractmenus/util/TimeParser.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/TimeParser.java rename to plugin/src/main/java/ru/abstractmenus/util/TimeParser.java diff --git a/src/main/java/ru/abstractmenus/util/TimeUtil.java b/plugin/src/main/java/ru/abstractmenus/util/TimeUtil.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/TimeUtil.java rename to plugin/src/main/java/ru/abstractmenus/util/TimeUtil.java diff --git a/src/main/java/ru/abstractmenus/util/Tuple.java b/plugin/src/main/java/ru/abstractmenus/util/Tuple.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/Tuple.java rename to plugin/src/main/java/ru/abstractmenus/util/Tuple.java diff --git a/src/main/java/ru/abstractmenus/util/bukkit/BukkitTasks.java b/plugin/src/main/java/ru/abstractmenus/util/bukkit/BukkitTasks.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/bukkit/BukkitTasks.java rename to plugin/src/main/java/ru/abstractmenus/util/bukkit/BukkitTasks.java diff --git a/src/main/java/ru/abstractmenus/util/bukkit/Events.java b/plugin/src/main/java/ru/abstractmenus/util/bukkit/Events.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/bukkit/Events.java rename to plugin/src/main/java/ru/abstractmenus/util/bukkit/Events.java diff --git a/src/main/java/ru/abstractmenus/util/bukkit/ItemUtil.java b/plugin/src/main/java/ru/abstractmenus/util/bukkit/ItemUtil.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/bukkit/ItemUtil.java rename to plugin/src/main/java/ru/abstractmenus/util/bukkit/ItemUtil.java diff --git a/src/main/java/ru/abstractmenus/util/bukkit/MojangApi.java b/plugin/src/main/java/ru/abstractmenus/util/bukkit/MojangApi.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/bukkit/MojangApi.java rename to plugin/src/main/java/ru/abstractmenus/util/bukkit/MojangApi.java diff --git a/src/main/java/ru/abstractmenus/util/bukkit/Skulls.java b/plugin/src/main/java/ru/abstractmenus/util/bukkit/Skulls.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/bukkit/Skulls.java rename to plugin/src/main/java/ru/abstractmenus/util/bukkit/Skulls.java diff --git a/src/main/java/ru/abstractmenus/util/bukkit/TaskHandle.java b/plugin/src/main/java/ru/abstractmenus/util/bukkit/TaskHandle.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/bukkit/TaskHandle.java rename to plugin/src/main/java/ru/abstractmenus/util/bukkit/TaskHandle.java diff --git a/src/main/java/ru/abstractmenus/util/bukkit/UuidUtil.java b/plugin/src/main/java/ru/abstractmenus/util/bukkit/UuidUtil.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/bukkit/UuidUtil.java rename to plugin/src/main/java/ru/abstractmenus/util/bukkit/UuidUtil.java diff --git a/src/main/java/ru/abstractmenus/util/proxy/ClassInfo.java b/plugin/src/main/java/ru/abstractmenus/util/proxy/ClassInfo.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/proxy/ClassInfo.java rename to plugin/src/main/java/ru/abstractmenus/util/proxy/ClassInfo.java diff --git a/src/main/java/ru/abstractmenus/util/proxy/ReflectionUtil.java b/plugin/src/main/java/ru/abstractmenus/util/proxy/ReflectionUtil.java similarity index 100% rename from src/main/java/ru/abstractmenus/util/proxy/ReflectionUtil.java rename to plugin/src/main/java/ru/abstractmenus/util/proxy/ReflectionUtil.java diff --git a/src/main/java/ru/abstractmenus/variables/VarBuilderImpl.java b/plugin/src/main/java/ru/abstractmenus/variables/VarBuilderImpl.java similarity index 100% rename from src/main/java/ru/abstractmenus/variables/VarBuilderImpl.java rename to plugin/src/main/java/ru/abstractmenus/variables/VarBuilderImpl.java diff --git a/src/main/java/ru/abstractmenus/variables/VarData.java b/plugin/src/main/java/ru/abstractmenus/variables/VarData.java similarity index 100% rename from src/main/java/ru/abstractmenus/variables/VarData.java rename to plugin/src/main/java/ru/abstractmenus/variables/VarData.java diff --git a/src/main/java/ru/abstractmenus/variables/VarImpl.java b/plugin/src/main/java/ru/abstractmenus/variables/VarImpl.java similarity index 100% rename from src/main/java/ru/abstractmenus/variables/VarImpl.java rename to plugin/src/main/java/ru/abstractmenus/variables/VarImpl.java diff --git a/src/main/java/ru/abstractmenus/variables/VarNumData.java b/plugin/src/main/java/ru/abstractmenus/variables/VarNumData.java similarity index 100% rename from src/main/java/ru/abstractmenus/variables/VarNumData.java rename to plugin/src/main/java/ru/abstractmenus/variables/VarNumData.java diff --git a/src/main/java/ru/abstractmenus/variables/VariableManagerImpl.java b/plugin/src/main/java/ru/abstractmenus/variables/VariableManagerImpl.java similarity index 100% rename from src/main/java/ru/abstractmenus/variables/VariableManagerImpl.java rename to plugin/src/main/java/ru/abstractmenus/variables/VariableManagerImpl.java diff --git a/src/main/java/ru/abstractmenus/variables/VariablesDao.java b/plugin/src/main/java/ru/abstractmenus/variables/VariablesDao.java similarity index 100% rename from src/main/java/ru/abstractmenus/variables/VariablesDao.java rename to plugin/src/main/java/ru/abstractmenus/variables/VariablesDao.java diff --git a/src/main/resources/1_13/menu_anim.conf b/plugin/src/main/resources/1_13/menu_anim.conf similarity index 100% rename from src/main/resources/1_13/menu_anim.conf rename to plugin/src/main/resources/1_13/menu_anim.conf diff --git a/src/main/resources/1_21/menu_anim.conf b/plugin/src/main/resources/1_21/menu_anim.conf similarity index 100% rename from src/main/resources/1_21/menu_anim.conf rename to plugin/src/main/resources/1_21/menu_anim.conf diff --git a/src/main/resources/LICENSES b/plugin/src/main/resources/LICENSES similarity index 100% rename from src/main/resources/LICENSES rename to plugin/src/main/resources/LICENSES diff --git a/src/main/resources/animated_heads.conf b/plugin/src/main/resources/animated_heads.conf similarity index 100% rename from src/main/resources/animated_heads.conf rename to plugin/src/main/resources/animated_heads.conf diff --git a/src/main/resources/bench-menus/README.md b/plugin/src/main/resources/bench-menus/README.md similarity index 100% rename from src/main/resources/bench-menus/README.md rename to plugin/src/main/resources/bench-menus/README.md diff --git a/src/main/resources/bench-menus/_templates.conf b/plugin/src/main/resources/bench-menus/_templates.conf similarity index 100% rename from src/main/resources/bench-menus/_templates.conf rename to plugin/src/main/resources/bench-menus/_templates.conf diff --git a/src/main/resources/bench-menus/bench_actions_rich.conf b/plugin/src/main/resources/bench-menus/bench_actions_rich.conf similarity index 100% rename from src/main/resources/bench-menus/bench_actions_rich.conf rename to plugin/src/main/resources/bench-menus/bench_actions_rich.conf diff --git a/src/main/resources/bench-menus/bench_animated_carousel.conf b/plugin/src/main/resources/bench-menus/bench_animated_carousel.conf similarity index 100% rename from src/main/resources/bench-menus/bench_animated_carousel.conf rename to plugin/src/main/resources/bench-menus/bench_animated_carousel.conf diff --git a/src/main/resources/bench-menus/bench_complex_rules.conf b/plugin/src/main/resources/bench-menus/bench_complex_rules.conf similarity index 100% rename from src/main/resources/bench-menus/bench_complex_rules.conf rename to plugin/src/main/resources/bench-menus/bench_complex_rules.conf diff --git a/src/main/resources/bench-menus/bench_dynamic_lore.conf b/plugin/src/main/resources/bench-menus/bench_dynamic_lore.conf similarity index 100% rename from src/main/resources/bench-menus/bench_dynamic_lore.conf rename to plugin/src/main/resources/bench-menus/bench_dynamic_lore.conf diff --git a/src/main/resources/bench-menus/bench_generated_pagination.conf b/plugin/src/main/resources/bench-menus/bench_generated_pagination.conf similarity index 100% rename from src/main/resources/bench-menus/bench_generated_pagination.conf rename to plugin/src/main/resources/bench-menus/bench_generated_pagination.conf diff --git a/src/main/resources/bench-menus/bench_high_refresh.conf b/plugin/src/main/resources/bench-menus/bench_high_refresh.conf similarity index 100% rename from src/main/resources/bench-menus/bench_high_refresh.conf rename to plugin/src/main/resources/bench-menus/bench_high_refresh.conf diff --git a/src/main/resources/bench-menus/bench_minimessage_heavy.conf b/plugin/src/main/resources/bench-menus/bench_minimessage_heavy.conf similarity index 100% rename from src/main/resources/bench-menus/bench_minimessage_heavy.conf rename to plugin/src/main/resources/bench-menus/bench_minimessage_heavy.conf diff --git a/src/main/resources/bench-menus/bench_skull_grid.conf b/plugin/src/main/resources/bench-menus/bench_skull_grid.conf similarity index 100% rename from src/main/resources/bench-menus/bench_skull_grid.conf rename to plugin/src/main/resources/bench-menus/bench_skull_grid.conf diff --git a/src/main/resources/bench-menus/bench_static_grid.conf b/plugin/src/main/resources/bench-menus/bench_static_grid.conf similarity index 100% rename from src/main/resources/bench-menus/bench_static_grid.conf rename to plugin/src/main/resources/bench-menus/bench_static_grid.conf diff --git a/src/main/resources/bench-menus/bench_variables.conf b/plugin/src/main/resources/bench-menus/bench_variables.conf similarity index 100% rename from src/main/resources/bench-menus/bench_variables.conf rename to plugin/src/main/resources/bench-menus/bench_variables.conf diff --git a/src/main/resources/config.conf b/plugin/src/main/resources/config.conf similarity index 100% rename from src/main/resources/config.conf rename to plugin/src/main/resources/config.conf diff --git a/src/main/resources/menu.conf b/plugin/src/main/resources/menu.conf similarity index 100% rename from src/main/resources/menu.conf rename to plugin/src/main/resources/menu.conf diff --git a/src/main/resources/menu_anim.conf b/plugin/src/main/resources/menu_anim.conf similarity index 100% rename from src/main/resources/menu_anim.conf rename to plugin/src/main/resources/menu_anim.conf diff --git a/src/main/resources/plugin.yml b/plugin/src/main/resources/plugin.yml similarity index 100% rename from src/main/resources/plugin.yml rename to plugin/src/main/resources/plugin.yml diff --git a/src/main/resources/variables.sql b/plugin/src/main/resources/variables.sql similarity index 100% rename from src/main/resources/variables.sql rename to plugin/src/main/resources/variables.sql diff --git a/src/test/java/ru/abstractmenus/bench/ConcurrencyBenchmark.java b/plugin/src/test/java/ru/abstractmenus/bench/ConcurrencyBenchmark.java similarity index 100% rename from src/test/java/ru/abstractmenus/bench/ConcurrencyBenchmark.java rename to plugin/src/test/java/ru/abstractmenus/bench/ConcurrencyBenchmark.java diff --git a/src/test/java/ru/abstractmenus/bench/ItemBuildBenchmark.java b/plugin/src/test/java/ru/abstractmenus/bench/ItemBuildBenchmark.java similarity index 100% rename from src/test/java/ru/abstractmenus/bench/ItemBuildBenchmark.java rename to plugin/src/test/java/ru/abstractmenus/bench/ItemBuildBenchmark.java diff --git a/src/test/java/ru/abstractmenus/bench/PlaceholderReplaceBenchmark.java b/plugin/src/test/java/ru/abstractmenus/bench/PlaceholderReplaceBenchmark.java similarity index 100% rename from src/test/java/ru/abstractmenus/bench/PlaceholderReplaceBenchmark.java rename to plugin/src/test/java/ru/abstractmenus/bench/PlaceholderReplaceBenchmark.java diff --git a/src/test/java/ru/abstractmenus/bench/RunBenchmarks.java b/plugin/src/test/java/ru/abstractmenus/bench/RunBenchmarks.java similarity index 100% rename from src/test/java/ru/abstractmenus/bench/RunBenchmarks.java rename to plugin/src/test/java/ru/abstractmenus/bench/RunBenchmarks.java diff --git a/src/test/java/ru/abstractmenus/bench/SlotContainsBenchmark.java b/plugin/src/test/java/ru/abstractmenus/bench/SlotContainsBenchmark.java similarity index 100% rename from src/test/java/ru/abstractmenus/bench/SlotContainsBenchmark.java rename to plugin/src/test/java/ru/abstractmenus/bench/SlotContainsBenchmark.java diff --git a/src/test/java/ru/abstractmenus/bench/StringSplitBenchmark.java b/plugin/src/test/java/ru/abstractmenus/bench/StringSplitBenchmark.java similarity index 100% rename from src/test/java/ru/abstractmenus/bench/StringSplitBenchmark.java rename to plugin/src/test/java/ru/abstractmenus/bench/StringSplitBenchmark.java diff --git a/src/test/java/ru/abstractmenus/data/actions/TestActionCommandBehavior.java b/plugin/src/test/java/ru/abstractmenus/data/actions/TestActionCommandBehavior.java similarity index 100% rename from src/test/java/ru/abstractmenus/data/actions/TestActionCommandBehavior.java rename to plugin/src/test/java/ru/abstractmenus/data/actions/TestActionCommandBehavior.java diff --git a/src/test/java/ru/abstractmenus/data/properties/TestPropNameLorePrecompute.java b/plugin/src/test/java/ru/abstractmenus/data/properties/TestPropNameLorePrecompute.java similarity index 100% rename from src/test/java/ru/abstractmenus/data/properties/TestPropNameLorePrecompute.java rename to plugin/src/test/java/ru/abstractmenus/data/properties/TestPropNameLorePrecompute.java diff --git a/src/test/java/ru/abstractmenus/datatype/TestCompoundDataTypes.java b/plugin/src/test/java/ru/abstractmenus/datatype/TestCompoundDataTypes.java similarity index 100% rename from src/test/java/ru/abstractmenus/datatype/TestCompoundDataTypes.java rename to plugin/src/test/java/ru/abstractmenus/datatype/TestCompoundDataTypes.java diff --git a/src/test/java/ru/abstractmenus/datatype/TestHasPlaceholder.java b/plugin/src/test/java/ru/abstractmenus/datatype/TestHasPlaceholder.java similarity index 100% rename from src/test/java/ru/abstractmenus/datatype/TestHasPlaceholder.java rename to plugin/src/test/java/ru/abstractmenus/datatype/TestHasPlaceholder.java diff --git a/src/test/java/ru/abstractmenus/datatype/TestPrimitiveDataTypes.java b/plugin/src/test/java/ru/abstractmenus/datatype/TestPrimitiveDataTypes.java similarity index 100% rename from src/test/java/ru/abstractmenus/datatype/TestPrimitiveDataTypes.java rename to plugin/src/test/java/ru/abstractmenus/datatype/TestPrimitiveDataTypes.java diff --git a/src/test/java/ru/abstractmenus/datatype/TestTypeMaterial.java b/plugin/src/test/java/ru/abstractmenus/datatype/TestTypeMaterial.java similarity index 100% rename from src/test/java/ru/abstractmenus/datatype/TestTypeMaterial.java rename to plugin/src/test/java/ru/abstractmenus/datatype/TestTypeMaterial.java diff --git a/src/test/java/ru/abstractmenus/datatype/TestTypeMaterialSerializer.java b/plugin/src/test/java/ru/abstractmenus/datatype/TestTypeMaterialSerializer.java similarity index 100% rename from src/test/java/ru/abstractmenus/datatype/TestTypeMaterialSerializer.java rename to plugin/src/test/java/ru/abstractmenus/datatype/TestTypeMaterialSerializer.java diff --git a/src/test/java/ru/abstractmenus/integration/TestPluginLifecycle.java b/plugin/src/test/java/ru/abstractmenus/integration/TestPluginLifecycle.java similarity index 100% rename from src/test/java/ru/abstractmenus/integration/TestPluginLifecycle.java rename to plugin/src/test/java/ru/abstractmenus/integration/TestPluginLifecycle.java diff --git a/src/test/java/ru/abstractmenus/menu/item/TestSimpleItemCopyOnWrite.java b/plugin/src/test/java/ru/abstractmenus/menu/item/TestSimpleItemCopyOnWrite.java similarity index 100% rename from src/test/java/ru/abstractmenus/menu/item/TestSimpleItemCopyOnWrite.java rename to plugin/src/test/java/ru/abstractmenus/menu/item/TestSimpleItemCopyOnWrite.java diff --git a/src/test/java/ru/abstractmenus/serializers/TestColorSerializer.java b/plugin/src/test/java/ru/abstractmenus/serializers/TestColorSerializer.java similarity index 100% rename from src/test/java/ru/abstractmenus/serializers/TestColorSerializer.java rename to plugin/src/test/java/ru/abstractmenus/serializers/TestColorSerializer.java diff --git a/src/test/java/ru/abstractmenus/serializers/TestEntityTypeSerializer.java b/plugin/src/test/java/ru/abstractmenus/serializers/TestEntityTypeSerializer.java similarity index 100% rename from src/test/java/ru/abstractmenus/serializers/TestEntityTypeSerializer.java rename to plugin/src/test/java/ru/abstractmenus/serializers/TestEntityTypeSerializer.java diff --git a/src/test/java/ru/abstractmenus/serializers/TestJsonSerializer.java b/plugin/src/test/java/ru/abstractmenus/serializers/TestJsonSerializer.java similarity index 100% rename from src/test/java/ru/abstractmenus/serializers/TestJsonSerializer.java rename to plugin/src/test/java/ru/abstractmenus/serializers/TestJsonSerializer.java diff --git a/src/test/java/ru/abstractmenus/util/TestFileUtils.java b/plugin/src/test/java/ru/abstractmenus/util/TestFileUtils.java similarity index 100% rename from src/test/java/ru/abstractmenus/util/TestFileUtils.java rename to plugin/src/test/java/ru/abstractmenus/util/TestFileUtils.java diff --git a/src/test/java/ru/abstractmenus/util/TestLegacyColorTagReplacer.java b/plugin/src/test/java/ru/abstractmenus/util/TestLegacyColorTagReplacer.java similarity index 100% rename from src/test/java/ru/abstractmenus/util/TestLegacyColorTagReplacer.java rename to plugin/src/test/java/ru/abstractmenus/util/TestLegacyColorTagReplacer.java diff --git a/src/test/java/ru/abstractmenus/util/TestNumberUtil.java b/plugin/src/test/java/ru/abstractmenus/util/TestNumberUtil.java similarity index 100% rename from src/test/java/ru/abstractmenus/util/TestNumberUtil.java rename to plugin/src/test/java/ru/abstractmenus/util/TestNumberUtil.java diff --git a/src/test/java/ru/abstractmenus/util/TestSlotUtil.java b/plugin/src/test/java/ru/abstractmenus/util/TestSlotUtil.java similarity index 100% rename from src/test/java/ru/abstractmenus/util/TestSlotUtil.java rename to plugin/src/test/java/ru/abstractmenus/util/TestSlotUtil.java diff --git a/src/test/java/ru/abstractmenus/util/TestStringUtil.java b/plugin/src/test/java/ru/abstractmenus/util/TestStringUtil.java similarity index 100% rename from src/test/java/ru/abstractmenus/util/TestStringUtil.java rename to plugin/src/test/java/ru/abstractmenus/util/TestStringUtil.java diff --git a/src/test/java/ru/abstractmenus/util/TestTimeParser.java b/plugin/src/test/java/ru/abstractmenus/util/TestTimeParser.java similarity index 100% rename from src/test/java/ru/abstractmenus/util/TestTimeParser.java rename to plugin/src/test/java/ru/abstractmenus/util/TestTimeParser.java diff --git a/src/test/resources/test.conf b/plugin/src/test/resources/test.conf similarity index 100% rename from src/test/resources/test.conf rename to plugin/src/test/resources/test.conf From 1fd6cd6fc0c482cc6290287e075df9f3c2ae2c8f Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Wed, 22 Apr 2026 17:33:56 +0500 Subject: [PATCH 03/71] build: introduce multi-module root (settings + common subprojects config) Co-Authored-By: Claude Opus 4.7 (1M context) --- api/.gitkeep | 0 build.gradle | 30 ++++++++++++++++++++++++++++++ settings.gradle | 5 ++++- 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 api/.gitkeep create mode 100644 build.gradle diff --git a/api/.gitkeep b/api/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..3b5ace6 --- /dev/null +++ b/build.gradle @@ -0,0 +1,30 @@ +// Common configuration applied to every subproject. +// Subproject-specific plugins (java-library, shadow, paperweight) stay in subprojects. + +allprojects { + group = 'ru.abstractmenus' + version = '1.18.0' // bumped to 2.0.0-alpha in Task 13 + + repositories { + mavenLocal() + mavenCentral() + maven { url 'https://repo.papermc.io/repository/maven-public/' } + maven { url 'https://jitpack.io' } + } +} + +subprojects { + apply plugin: 'java' + + java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } + } + + test { + useJUnitPlatform() + } +} diff --git a/settings.gradle b/settings.gradle index 127b032..610006e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,4 @@ -rootProject.name = 'AbstractMenus' \ No newline at end of file +rootProject.name = 'AbstractMenus' + +include 'api' +include 'plugin' \ No newline at end of file From 6351c861ff963263ef8004c7c6109a33c14151db Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Wed, 22 Apr 2026 17:36:52 +0500 Subject: [PATCH 04/71] build: adapt plugin/build.gradle to new location (drop root-supplied config) Remove group/version, java toolchain block, and repos already declared in root (mavenLocal, mavenCentral, paper, jitpack); drop redundant 'java' plugin application and duplicate test { useJUnitPlatform() }; fix archiveFileName to hardcode 'AbstractMenus-' prefix so the shadow artifact is not named 'plugin-.jar'. Co-Authored-By: Claude Opus 4.7 (1M context) --- plugin/build.gradle | 65 +++++++++++++-------------------------------- 1 file changed, 19 insertions(+), 46 deletions(-) diff --git a/plugin/build.gradle b/plugin/build.gradle index ef7e150..a858452 100644 --- a/plugin/build.gradle +++ b/plugin/build.gradle @@ -11,49 +11,28 @@ buildscript { } plugins { - id 'java' - id 'com.gradleup.shadow' version '9.4.1' - id("io.papermc.paperweight.userdev") version "2.0.0-beta.21" + id 'com.gradleup.shadow' version '9.4.1' + id 'io.papermc.paperweight.userdev' version '2.0.0-beta.21' } -java { - sourceCompatibility = "21" - targetCompatibility = "21" - toolchain { - languageVersion.set(JavaLanguageVersion.of(21)) - } -} - - -group 'ru.abstractmenus' -version '1.18.0' - +// Plugin-specific repos not already in root repositories { - mavenLocal() - mavenCentral() - maven { url 'https://repo.papermc.io/repository/maven-public/' } - maven { url 'https://jitpack.io' } maven { url 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' } maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } maven { url 'https://maven.enginehub.org/repo/' } maven { url 'https://repo.extendedclip.com/content/repositories/placeholderapi/' } maven { url 'https://mvn.lumine.io/repository/maven-public/' } - maven { url 'https://repo.codemc.org/repository/maven-public/' - metadataSources { - artifact() - } + maven { + url 'https://repo.codemc.org/repository/maven-public/' + metadataSources { artifact() } } maven { url 'https://repo.codemc.org/repository/maven-snapshots/' - metadataSources { - artifact() - } + metadataSources { artifact() } } maven { url 'https://repo.codemc.org/repository/maven-releases/' - metadataSources { - artifact() - } + metadataSources { artifact() } } } @@ -62,30 +41,32 @@ dependencies { annotationProcessor 'org.projectlombok:lombok:1.18.44' paperweight.paperDevBundle("1.21.11-R0.1-SNAPSHOT") + + // NOTE: was 'com.github.AbstractMenus:api:995cd8c9a9' — swapped in Task 8 implementation 'com.github.AbstractMenus:api:995cd8c9a9' + implementation 'com.fathzer:javaluator:3.0.6' implementation 'net.kyori:adventure-platform-bukkit:4.4.1' implementation 'net.kyori:adventure-text-minimessage:4.26.1' implementation "com.github.technicallycoded:FoliaLib:0.4.4" -// shadow files('libs/spigot-1.14.4.jar') shadow files('libs/MMOItems-6.5.4.jar') shadow files('libs/citizens-2.0.jar') shadow ('de.tr7zw:item-nbt-api-plugin:2.15.7') { exclude group: 'org.bstats', module: 'bstats-bukkit' } - shadow ('com.arcaniax:HeadDatabase-API:1.3.2'){ + shadow ('com.arcaniax:HeadDatabase-API:1.3.2') { exclude group: 'org.bstats', module: 'bstats-bukkit' } - shadow ('com.github.MilkBowl:VaultAPI:1.7.1'){ + shadow ('com.github.MilkBowl:VaultAPI:1.7.1') { exclude group: 'org.bstats', module: 'bstats-bukkit' exclude group: 'org.bukkit' } - shadow ('net.luckperms:api:5.5'){ + shadow ('net.luckperms:api:5.5') { exclude group: 'org.bstats', module: 'bstats-bukkit' } - shadow ('com.sk89q.worldguard:worldguard-bukkit:7.0.0'){ + shadow ('com.sk89q.worldguard:worldguard-bukkit:7.0.0') { exclude group: 'org.bstats', module: 'bstats-bukkit' exclude group: 'org.bukkit' exclude group: 'io.papermc.paper' @@ -93,30 +74,24 @@ dependencies { compileOnly 'me.clip:placeholderapi:2.12.2' - shadow ('io.lumine:MythicLib:1.0.12-SNAPSHOT'){ + shadow ('io.lumine:MythicLib:1.0.12-SNAPSHOT') { exclude group: 'org.bstats', module: 'bstats-bukkit' } - shadow ('com.github.LoneDev6:api-itemsadder:3.6.1'){ + shadow ('com.github.LoneDev6:api-itemsadder:3.6.1') { exclude group: 'org.bstats', module: 'bstats-bukkit' } - shadow ('net.skinsrestorer:skinsrestorer-api:15.10.0'){ + shadow ('net.skinsrestorer:skinsrestorer-api:15.10.0') { exclude group: 'org.bstats', module: 'bstats-bukkit' } testImplementation 'org.junit.jupiter:junit-jupiter-api:5.14.3' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.14.3' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' - - // MockBukkit — in-memory Paper API mock for integration tests testImplementation 'org.mockbukkit.mockbukkit:mockbukkit-v1.21:4.108.0' - - // Mockito — isolated behavior tests against Bukkit API surface testImplementation 'org.mockito:mockito-core:5.15.2' testImplementation 'org.mockito:mockito-junit-jupiter:5.15.2' - testImplementation 'org.openjdk.jmh:jmh-core:1.37' testAnnotationProcessor 'org.openjdk.jmh:jmh-generator-annprocess:1.37' - testCompileOnly 'org.projectlombok:lombok:1.18.44' testAnnotationProcessor 'org.projectlombok:lombok:1.18.44' } @@ -132,12 +107,10 @@ shadowJar { dependencies { exclude dependency('org.inventivetalent.spiget-update:bukkit') } - - archiveFileName.set("${project.name}-${project.version}.jar") + archiveFileName.set("AbstractMenus-${project.version}.jar") } test { - useJUnitPlatform() exclude 'ru/abstractmenus/bench/**' } From b8e02972d1f30e28f1d18a903772721063bebf66 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Wed, 22 Apr 2026 17:39:11 +0500 Subject: [PATCH 05/71] build: add empty :api subproject skeleton (java-library, paper 1.21.11) Co-Authored-By: Claude Opus 4.7 (1M context) --- api/build.gradle | 29 +++++++++++++++++++++++++++++ api/{ => src/main/java}/.gitkeep | 0 2 files changed, 29 insertions(+) create mode 100644 api/build.gradle rename api/{ => src/main/java}/.gitkeep (100%) diff --git a/api/build.gradle b/api/build.gradle new file mode 100644 index 0000000..25fc8fc --- /dev/null +++ b/api/build.gradle @@ -0,0 +1,29 @@ +plugins { + id 'java-library' +} + +repositories { + // root already supplies papermc + jitpack + mavenCentral + mavenLocal +} + +dependencies { + compileOnly 'io.papermc.paper:paper-api:1.21.11-R0.1-SNAPSHOT' + api 'com.github.AbstractMenus:hocon:1.0.6' + + compileOnly 'org.projectlombok:lombok:1.18.44' + annotationProcessor 'org.projectlombok:lombok:1.18.44' + + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.14.3' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.14.3' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' +} + +java { + withSourcesJar() + withJavadocJar() +} + +javadoc { + title = "AbstractMenus ${project.version} API" + options.addStringOption('Xdoclint:none', '-quiet') +} diff --git a/api/.gitkeep b/api/src/main/java/.gitkeep similarity index 100% rename from api/.gitkeep rename to api/src/main/java/.gitkeep From 8b1fceb1034f6f6f0b7feb36775d3980f1333352 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Wed, 22 Apr 2026 17:42:11 +0500 Subject: [PATCH 06/71] feat(api): import sources from abstract-menus-api repo (verbatim, no changes) Co-Authored-By: Claude Opus 4.7 (1M context) --- api/src/main/java/.gitkeep | 0 .../api/AbstractMenusPlugin.java | 55 ++++++ .../api/AbstractMenusProvider.java | 28 +++ .../java/ru/abstractmenus/api/Action.java | 21 ++ .../java/ru/abstractmenus/api/Activator.java | 43 +++++ .../java/ru/abstractmenus/api/Catalog.java | 28 +++ .../java/ru/abstractmenus/api/Handlers.java | 97 ++++++++++ .../java/ru/abstractmenus/api/Logger.java | 44 +++++ .../main/java/ru/abstractmenus/api/Rule.java | 22 +++ .../main/java/ru/abstractmenus/api/Types.java | 180 ++++++++++++++++++ .../ru/abstractmenus/api/ValueExtractor.java | 18 ++ .../api/handler/EconomyHandler.java | 32 ++++ .../api/handler/LevelHandler.java | 52 +++++ .../api/handler/PermissionsHandler.java | 54 ++++++ .../api/handler/PlaceholderHandler.java | 41 ++++ .../api/handler/SkinHandler.java | 24 +++ .../ru/abstractmenus/api/inventory/Item.java | 60 ++++++ .../api/inventory/ItemProperty.java | 43 +++++ .../ru/abstractmenus/api/inventory/Menu.java | 122 ++++++++++++ .../ru/abstractmenus/api/inventory/Slot.java | 18 ++ .../api/inventory/slot/SlotIndex.java | 35 ++++ .../api/inventory/slot/SlotMatrix.java | 31 +++ .../api/inventory/slot/SlotPos.java | 28 +++ .../api/inventory/slot/SlotRange.java | 30 +++ .../ru/abstractmenus/api/text/Colors.java | 107 +++++++++++ .../ru/abstractmenus/api/variables/Var.java | 82 ++++++++ .../api/variables/VarBuilder.java | 54 ++++++ .../api/variables/VariableManager.java | 74 +++++++ 28 files changed, 1423 insertions(+) delete mode 100644 api/src/main/java/.gitkeep create mode 100644 api/src/main/java/ru/abstractmenus/api/AbstractMenusPlugin.java create mode 100644 api/src/main/java/ru/abstractmenus/api/AbstractMenusProvider.java create mode 100644 api/src/main/java/ru/abstractmenus/api/Action.java create mode 100644 api/src/main/java/ru/abstractmenus/api/Activator.java create mode 100644 api/src/main/java/ru/abstractmenus/api/Catalog.java create mode 100644 api/src/main/java/ru/abstractmenus/api/Handlers.java create mode 100644 api/src/main/java/ru/abstractmenus/api/Logger.java create mode 100644 api/src/main/java/ru/abstractmenus/api/Rule.java create mode 100644 api/src/main/java/ru/abstractmenus/api/Types.java create mode 100644 api/src/main/java/ru/abstractmenus/api/ValueExtractor.java create mode 100644 api/src/main/java/ru/abstractmenus/api/handler/EconomyHandler.java create mode 100644 api/src/main/java/ru/abstractmenus/api/handler/LevelHandler.java create mode 100644 api/src/main/java/ru/abstractmenus/api/handler/PermissionsHandler.java create mode 100644 api/src/main/java/ru/abstractmenus/api/handler/PlaceholderHandler.java create mode 100644 api/src/main/java/ru/abstractmenus/api/handler/SkinHandler.java create mode 100644 api/src/main/java/ru/abstractmenus/api/inventory/Item.java create mode 100644 api/src/main/java/ru/abstractmenus/api/inventory/ItemProperty.java create mode 100644 api/src/main/java/ru/abstractmenus/api/inventory/Menu.java create mode 100644 api/src/main/java/ru/abstractmenus/api/inventory/Slot.java create mode 100644 api/src/main/java/ru/abstractmenus/api/inventory/slot/SlotIndex.java create mode 100644 api/src/main/java/ru/abstractmenus/api/inventory/slot/SlotMatrix.java create mode 100644 api/src/main/java/ru/abstractmenus/api/inventory/slot/SlotPos.java create mode 100644 api/src/main/java/ru/abstractmenus/api/inventory/slot/SlotRange.java create mode 100644 api/src/main/java/ru/abstractmenus/api/text/Colors.java create mode 100644 api/src/main/java/ru/abstractmenus/api/variables/Var.java create mode 100644 api/src/main/java/ru/abstractmenus/api/variables/VarBuilder.java create mode 100644 api/src/main/java/ru/abstractmenus/api/variables/VariableManager.java diff --git a/api/src/main/java/.gitkeep b/api/src/main/java/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/api/src/main/java/ru/abstractmenus/api/AbstractMenusPlugin.java b/api/src/main/java/ru/abstractmenus/api/AbstractMenusPlugin.java new file mode 100644 index 0000000..3d5d065 --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/AbstractMenusPlugin.java @@ -0,0 +1,55 @@ +package ru.abstractmenus.api; + +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import ru.abstractmenus.api.inventory.Menu; +import ru.abstractmenus.api.variables.VariableManager; + +import java.util.Optional; + +/** + * Base plugin interface + */ +public interface AbstractMenusPlugin { + + /** + * Get plugin instance + * @return Plugin instance + */ + Plugin getPlugin(); + + /** + * Get variables manager + * @return Variables manager instance + */ + VariableManager getVariableManager(); + + /** + * Reload all menus + */ + void loadMenus(); + + /** + * Open menu for a player with activator and context + * @param activator Activator which caused opening. Might be null + * @param ctx Opening context (object that causes opening) + * @param player Menu viewer + * @param menu Menu to open + */ + void openMenu(Activator activator, Object ctx, Player player, Menu menu); + + /** + * Open menu for a player + * @param player Menu viewer + * @param menu Menu to open + */ + void openMenu(Player player, Menu menu); + + /** + * Get opened menu by player who opened this menu + * @param player Menu viewer + * @return Found menu in Optional wrapper or Optional.EMPTY otherwise + */ + Optional getOpenedMenu(Player player); + +} diff --git a/api/src/main/java/ru/abstractmenus/api/AbstractMenusProvider.java b/api/src/main/java/ru/abstractmenus/api/AbstractMenusProvider.java new file mode 100644 index 0000000..78ceec2 --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/AbstractMenusProvider.java @@ -0,0 +1,28 @@ +package ru.abstractmenus.api; + +/** + * Plugin provider. Provides access to plugin instance + */ +public final class AbstractMenusProvider { + + private static AbstractMenusPlugin plugin; + + private AbstractMenusProvider(){} + + /** + * Initialize plugin instance + * @param plug Plugin instance + */ + public static void init(AbstractMenusPlugin plug){ + plugin = plug; + } + + /** + * Get AbstractMenus plugin instance + * @return Plugin instance + */ + public static AbstractMenusPlugin get(){ + return plugin; + } + +} diff --git a/api/src/main/java/ru/abstractmenus/api/Action.java b/api/src/main/java/ru/abstractmenus/api/Action.java new file mode 100644 index 0000000..36a52b6 --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/Action.java @@ -0,0 +1,21 @@ +package ru.abstractmenus.api; + +import org.bukkit.entity.Player; +import ru.abstractmenus.api.inventory.Menu; +import ru.abstractmenus.api.inventory.Item; + +/** + * Represents menu action. + */ +@FunctionalInterface +public interface Action { + + /** + * Do action by provided data + * @param player Player who initiated this action + * @param menu Menu in which this action initiated + * @param clickedItem Item which initiated this action. Might be null + */ + void activate(Player player, Menu menu, Item clickedItem); + +} diff --git a/api/src/main/java/ru/abstractmenus/api/Activator.java b/api/src/main/java/ru/abstractmenus/api/Activator.java new file mode 100644 index 0000000..c159242 --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/Activator.java @@ -0,0 +1,43 @@ +package ru.abstractmenus.api; + +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; +import ru.abstractmenus.api.inventory.Menu; + +/** + * Menu activator. Activator will be registered as event listener. + * Do not register it manually, plugin does this automatically + */ +public abstract class Activator implements Listener { + + /** + * Menu instance + */ + protected Menu menu; + + /** + * Set menu to this activator + * @param menu Target menu + */ + public void setTargetMenu(Menu menu) { + this.menu = menu; + } + + /** + * Open menu for a player + * @param ctx Opening context. This can be Bukkit Event or something else + * @param player Player to open menu + */ + protected void openMenu(Object ctx, Player player) { + AbstractMenusProvider.get().openMenu(this, ctx, player, menu); + } + + /** + * Get value extractor for this activator. Can be null + * @return ValueExtractor instance + */ + public ValueExtractor getValueExtractor() { + return null; + } + +} diff --git a/api/src/main/java/ru/abstractmenus/api/Catalog.java b/api/src/main/java/ru/abstractmenus/api/Catalog.java new file mode 100644 index 0000000..9f7a962 --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/Catalog.java @@ -0,0 +1,28 @@ +package ru.abstractmenus.api; + +import org.bukkit.entity.Player; +import ru.abstractmenus.api.inventory.Menu; + +import java.util.Collection; + +/** + * Catalog is an object collection provider + * Catalog uses for the auto generated menus + */ +public interface Catalog { + + /** + * Provide collection of objects depend on player and menu state + * @param player Menu viewer + * @param menu Current menu + * @return Collection of objects + */ + Collection snapshot(Player player, Menu menu); + + /** + * Get value extractor for values in this catalog + * @return Value extractor instance + */ + ValueExtractor extractor(); + +} diff --git a/api/src/main/java/ru/abstractmenus/api/Handlers.java b/api/src/main/java/ru/abstractmenus/api/Handlers.java new file mode 100644 index 0000000..1edcf7a --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/Handlers.java @@ -0,0 +1,97 @@ +package ru.abstractmenus.api; + +import ru.abstractmenus.api.handler.*; + +/** + * Data handlers provider + */ +public final class Handlers { + + private static EconomyHandler economyHandler; + private static PermissionsHandler permissionsHandler; + private static LevelHandler levelHandler; + private static PlaceholderHandler placeholderHandler; + private static SkinHandler skinHandler; + + private Handlers(){} + + /** + * Get registered economy handler + * @return Registered economy handler + */ + public static EconomyHandler getEconomyHandler() { + return economyHandler; + } + + /** + * Register own economy handler + * @param handler Economy handler implementation + */ + public static void setEconomyHandler(EconomyHandler handler) { + economyHandler = handler; + } + + /** + * Get registered permissions handler + * @return Registered permissions handler + */ + public static PermissionsHandler getPermissionsHandler() { + return permissionsHandler; + } + + /** + * Register own permissions handler + * @param handler Permissions handler implementation + */ + public static void setPermissionsHandler(PermissionsHandler handler) { + permissionsHandler = handler; + } + + /** + * Get registered level handler + * @return Registered level handler + */ + public static LevelHandler getLevelHandler() { + return levelHandler; + } + + /** + * Register own level handler + * @param handler Level handler implementation + */ + public static void setLevelHandler(LevelHandler handler) { + levelHandler = handler; + } + + /** + * Get registered placeholder handler + * @return Registered placeholder handler + */ + public static PlaceholderHandler getPlaceholderHandler() { + return placeholderHandler; + } + + /** + * Register own placeholder handler + * @param handler Placeholder handler implementation + */ + public static void setPlaceholderHandler(PlaceholderHandler handler) { + placeholderHandler = handler; + } + + /** + * Get registered skin handler + * @return Registered skin handler + */ + public static SkinHandler getSkinHandler() { + return skinHandler; + } + + /** + * Register own skin handler + * @param handler Skin handler implementation + */ + public static void setSkinHandler(SkinHandler handler) { + skinHandler = handler; + } +} diff --git a/api/src/main/java/ru/abstractmenus/api/Logger.java b/api/src/main/java/ru/abstractmenus/api/Logger.java new file mode 100644 index 0000000..6e8bc18 --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/Logger.java @@ -0,0 +1,44 @@ +package ru.abstractmenus.api; + +/** + * Simple static methods for logger + */ +public final class Logger { + + private static java.util.logging.Logger logger; + + private Logger(){} + + /** + * Set logger instance + * @param log Logger instance + */ + public static void set(java.util.logging.Logger log){ + logger = log; + } + + /** + * Log with INFO scope + * @param message Log message + */ + public static void info(String message){ + logger.info(message); + } + + /** + * Log with WARNING scope + * @param message Log message + */ + public static void warning(String message){ + logger.warning(message); + } + + /** + * Log with SEVERE (error) scope + * @param message Log message + */ + public static void severe(String message){ + logger.severe(message); + } + +} diff --git a/api/src/main/java/ru/abstractmenus/api/Rule.java b/api/src/main/java/ru/abstractmenus/api/Rule.java new file mode 100644 index 0000000..49ce929 --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/Rule.java @@ -0,0 +1,22 @@ +package ru.abstractmenus.api; + +import org.bukkit.entity.Player; +import ru.abstractmenus.api.inventory.Menu; +import ru.abstractmenus.api.inventory.Item; + +/** + * Represents the rule that the player must meets in order to proceed. + */ +@FunctionalInterface +public interface Rule { + + /** + * Check if the player meets the rule + * @param player Player to check + * @param menu Menu in which this rule works + * @param clickedItem An item which player might click and initiate this checking. Might be null + * @return true if player meets the rule or false of not + */ + boolean check(Player player, Menu menu, Item clickedItem); + +} diff --git a/api/src/main/java/ru/abstractmenus/api/Types.java b/api/src/main/java/ru/abstractmenus/api/Types.java new file mode 100644 index 0000000..bed0a7e --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/Types.java @@ -0,0 +1,180 @@ +package ru.abstractmenus.api; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import ru.abstractmenus.api.inventory.ItemProperty; +import ru.abstractmenus.hocon.api.serialize.NodeSerializer; +import ru.abstractmenus.hocon.api.serialize.NodeSerializers; + +/** + * Serializers provider. All data types for menus registering here + */ +public final class Types { + + private static final NodeSerializers SERIALIZERS = NodeSerializers.defaults(); + private static final BiMap> ACTION_TYPES = HashBiMap.create(); + private static final BiMap> RULE_TYPES = HashBiMap.create(); + private static final BiMap> ACTIVATOR_TYPES = HashBiMap.create(); + private static final BiMap> ITEM_PROPERTIES_TYPES = HashBiMap.create(); + private static final BiMap>> CATALOG_TYPES = HashBiMap.create(); + + private Types() { } + + /** + * Global serializers collection + * @return Serializers collection + */ + public static NodeSerializers serializers() { + return SERIALIZERS; + } + + /** + * Get action name by type + * @param type Action type + * @return Action name or null if not found + */ + public static String getActionName(Class type) { + return ACTION_TYPES.inverse().get(type); + } + + /** + * Get rule name by type + * @param type Rule type + * @return Rule name or null if not found + */ + public static String getRuleName(Class type) { + return RULE_TYPES.inverse().get(type); + } + + /** + * Get activator name by type + * @param type Activator type + * @return Activator name or null if not found + */ + public static String getActivatorName(Class type) { + return ACTIVATOR_TYPES.inverse().get(type); + } + + /** + * Get item property name by type + * @param type Item property type + * @return Item property name or null if not found + */ + public static String getItemPropertyName(Class type) { + return ITEM_PROPERTIES_TYPES.inverse().get(type); + } + + /** + * Get catalog name by type + * @param type Catalog type + * @return Catalog name or null if not found + */ + public static String getCatalogName(Class type) { + return CATALOG_TYPES.inverse().get(type); + } + + /** + * Register new action + * @param key Key of the action. This key uses in menu file + * @param token Type of the action + * @param serializer Serializer of the action + * @param Type of the action + */ + public static void registerAction(String key, Class token, NodeSerializer serializer){ + serializers().register(token, serializer); + ACTION_TYPES.put(key.toLowerCase(), token); + } + + /** + * Get token to deserialize action from menu file + * @param key Action key + * @return Found TypeToken of the registered action of null if token not found + */ + public static Class getActionType(String key){ + return ACTION_TYPES.get(key.toLowerCase()); + } + + /** + * Register new rule + * @param key Key of the rule. This key uses in menu file + * @param token Type of the rule + * @param serializer Serializer of the rule + * @param Type of the rule + */ + public static void registerRule(String key, Class token, NodeSerializer serializer){ + serializers().register(token, serializer); + RULE_TYPES.put(key.toLowerCase(), token); + } + + /** + * Get token to deserialize rule from menu file + * @param key Rule key + * @return Found TypeToken of the registered rule of null if token not found + */ + public static Class getRuleType(String key){ + return RULE_TYPES.get(key.toLowerCase()); + } + + /** + * Register new menu activator + * @param key Key of the activator. This key uses in menu file + * @param token Type of the activator + * @param serializer Serializer of the activator + * @param Type of the activator + */ + public static void registerActivator(String key, Class token, NodeSerializer serializer){ + serializers().register(token, serializer); + ACTIVATOR_TYPES.put(key.toLowerCase(), token); + } + + /** + * Get token to deserialize activator from menu file + * @param key Activator key + * @return Found TypeToken of the registered activator of null if token not found + */ + public static Class getActivator(String key){ + return ACTIVATOR_TYPES.get(key.toLowerCase()); + } + + /** + * Register new item property + * @param key Key of the property. This key uses in menu file + * @param token Type of the property + * @param serializer Serializer of the property + * @param Type of the property + */ + public static void registerItemProperty(String key, Class token, NodeSerializer serializer){ + serializers().register(token, serializer); + ITEM_PROPERTIES_TYPES.put(key.toLowerCase(), token); + } + + /** + * Get token to deserialize item property from menu file + * @param key Item property key + * @return Found TypeToken of the registered item property of null if token not found + */ + public static Class getItemPropertyType(String key){ + return ITEM_PROPERTIES_TYPES.get(key.toLowerCase()); + } + + /** + * Register new catalog + * @param key Key of the catalog. This key uses in menu file + * @param token Type of the catalog + * @param serializer Serializer of the catalog + * @param Type of the catalog + */ + public static > void registerCatalog(String key, Class token, NodeSerializer serializer){ + serializers().register(token, serializer); + CATALOG_TYPES.put(key.toLowerCase(), token); + } + + /** + * Get token to deserialize catalog from menu file + * @param key Catalog key + * @return Found TypeToken of the registered catalog of null if token not found + */ + public static Class> getCatalogType(String key){ + return CATALOG_TYPES.get(key.toLowerCase()); + } +} diff --git a/api/src/main/java/ru/abstractmenus/api/ValueExtractor.java b/api/src/main/java/ru/abstractmenus/api/ValueExtractor.java new file mode 100644 index 0000000..2bda33c --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/ValueExtractor.java @@ -0,0 +1,18 @@ +package ru.abstractmenus.api; + +/** + * Implementors of extractor accepts some + * object with placeholder and returns required data from this object + */ +public interface ValueExtractor { + + /** + * Get some data from given object by placeholder. + * You should cast object to required type first + * @param obj Any object + * @param placeholder Placeholder key. Example: "location_x" + * @return Resulting value in String format, or null + */ + String extract(Object obj, String placeholder); + +} diff --git a/api/src/main/java/ru/abstractmenus/api/handler/EconomyHandler.java b/api/src/main/java/ru/abstractmenus/api/handler/EconomyHandler.java new file mode 100644 index 0000000..ead566d --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/handler/EconomyHandler.java @@ -0,0 +1,32 @@ +package ru.abstractmenus.api.handler; + +import org.bukkit.entity.Player; + +/** + * Economy handler needs for economy actions and rules + */ +public interface EconomyHandler { + + /** + * Check is player has balance + * @param player Player to check + * @param balance Required balance + * @return true if player has required amount on balance + */ + boolean hasBalance(Player player, double balance); + + /** + * Withdraw some amount from player's balance + * @param player Required player + * @param amount Required amount + */ + void takeBalance(Player player, double amount); + + /** + * Give some amount to player's balance + * @param player Required player + * @param amount Required amount + */ + void giveBalance(Player player, double amount); + +} diff --git a/api/src/main/java/ru/abstractmenus/api/handler/LevelHandler.java b/api/src/main/java/ru/abstractmenus/api/handler/LevelHandler.java new file mode 100644 index 0000000..38aae38 --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/handler/LevelHandler.java @@ -0,0 +1,52 @@ +package ru.abstractmenus.api.handler; + +import org.bukkit.entity.Player; + +/** + * Level handler needs for level actions and rules + */ +public interface LevelHandler { + + /** + * Get player's exp + * @param player Required player + * @return Count of player's exp + */ + int getXp(Player player); + + /** + * Give some exp for player + * @param player Required player + * @param xp Number of experience + */ + void giveXp(Player player, int xp); + + /** + * Take some exp from player + * @param player Required player + * @param xp Number of experience + */ + void takeXp(Player player, int xp); + + /** + * Get player's level + * @param player Required player + * @return Number of player's level + */ + int getLevel(Player player); + + /** + * Give some level for player + * @param player Required player + * @param level Number of levels to add + */ + void giveLevel(Player player, int level); + + /** + * Take some level from player + * @param player Required player + * @param level Number of levels to withdraw + */ + void takeLevel(Player player, int level); + +} diff --git a/api/src/main/java/ru/abstractmenus/api/handler/PermissionsHandler.java b/api/src/main/java/ru/abstractmenus/api/handler/PermissionsHandler.java new file mode 100644 index 0000000..a9b5803 --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/handler/PermissionsHandler.java @@ -0,0 +1,54 @@ +package ru.abstractmenus.api.handler; + +import org.bukkit.entity.Player; + +/** + * Permissions handler needs for permission actions and rules + */ +public interface PermissionsHandler { + + /** + * Give permission to player + * @param player Required player + * @param permission Permission to give + */ + void addPermission(Player player, String permission); + + /** + * Remove permission from player + * @param player Required player + * @param permission Permission to remove + */ + void removePermission(Player player, String permission); + + /** + * Check is player has some permission + * @param player Required player + * @param permission Permission to check + * @return true if player has permission or false otherwise + */ + boolean hasPermission(Player player, String permission); + + /** + * Add player to specified permissions group + * @param player Required player + * @param group Group to add player to it + */ + void addGroup(Player player, String group); + + /** + * Remove player from specified permissions group + * @param player Required player + * @param group Group to remove player from it + */ + void removeGroup(Player player, String group); + + /** + * Check is player exist in specified permissions group + * @param player Required player + * @param group Group to check + * @return true if player member of group or false otherwise + */ + boolean hasGroup(Player player, String group); + +} diff --git a/api/src/main/java/ru/abstractmenus/api/handler/PlaceholderHandler.java b/api/src/main/java/ru/abstractmenus/api/handler/PlaceholderHandler.java new file mode 100644 index 0000000..b10f52b --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/handler/PlaceholderHandler.java @@ -0,0 +1,41 @@ +package ru.abstractmenus.api.handler; + +import org.bukkit.entity.Player; + +import java.util.List; + +/** + * Placeholder handler needs for placeholder replacing in items, actions, rules, etc. + */ +public interface PlaceholderHandler { + + /** + * Replace only given placeholder + * @param player Context player + * @param placeholder Placeholder key without '%' chars + * @return Replaced value + */ + String replacePlaceholder(Player player, String placeholder); + + /** + * Replace placeholders in single string + * @param player Player who opened menu + * @param str String to replace + * @return String with replaced placeholders + */ + String replace(Player player, String str); + + /** + * Replace placeholders in strings list + * @param player Player who opened menu + * @param list Strings list to replace + * @return String with replaced placeholders + */ + List replace(Player player, List list); + + /** + * Register placeholders + */ + void registerAll(); + +} diff --git a/api/src/main/java/ru/abstractmenus/api/handler/SkinHandler.java b/api/src/main/java/ru/abstractmenus/api/handler/SkinHandler.java new file mode 100644 index 0000000..8242c82 --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/handler/SkinHandler.java @@ -0,0 +1,24 @@ +package ru.abstractmenus.api.handler; + +import org.bukkit.entity.Player; + +/** + * Skins handler needs for skin actions and rules + */ +public interface SkinHandler { + + /** + * Set player's skin by provided texture and signature + * @param player Required player + * @param texture Skin texture + * @param signature Skin signature + */ + void setSkin(Player player, String texture, String signature); + + /** + * Reset player's skin to default + * @param player Required player + */ + void resetSkin(Player player); + +} diff --git a/api/src/main/java/ru/abstractmenus/api/inventory/Item.java b/api/src/main/java/ru/abstractmenus/api/inventory/Item.java new file mode 100644 index 0000000..dee5a0e --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/inventory/Item.java @@ -0,0 +1,60 @@ +package ru.abstractmenus.api.inventory; + +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.*; + +/** + * Represent buildable ItemStack + */ +public interface Item extends Cloneable { + + /** + * Get properties if the item + * @return List of item properties + */ + Map getProperties(); + + /** + * Add some property for this item + * @param key Property key + * @param property Item property + */ + void addProperty(String key, ItemProperty property); + + /** + * Set new or replace all properties for this item + * @param properties Properties map + */ + void setProperties(Map properties); + + /** + * Remove property from item + * @param key Key (name) of the property. This key you specify in item block in menu file + * @return Removed property or null if property with specified key is not assigned to item + */ + ItemProperty removeProperty(String key); + + /** + * Check is this item similar to som ItemStack + * @param item Bukkit ItemStack + * @param player Player to replace placeholders + * @return true if this item similar to provided or false otherwise + */ + boolean isSimilar(ItemStack item, Player player); + + /** + * Build ItemStack of this item + * @param player Player to correct replace all placeholders + * @param menu Menu in which this item exists. Might be null + * @return Built ItemStack object + */ + ItemStack build(Player player, Menu menu); + + /** + * Clone this item + * @return Cloned item + */ + Item clone(); +} diff --git a/api/src/main/java/ru/abstractmenus/api/inventory/ItemProperty.java b/api/src/main/java/ru/abstractmenus/api/inventory/ItemProperty.java new file mode 100644 index 0000000..8482549 --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/inventory/ItemProperty.java @@ -0,0 +1,43 @@ +package ru.abstractmenus.api.inventory; + +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +/** + * Represent any item property + */ +public interface ItemProperty { + + /** + * Is this property replaces item type of given item. + * + * Properties which replaces material always will be assigned first + * to create valid item meta for ItemStack + * + * @return true if this property replaces material or false otherwise + */ + boolean canReplaceMaterial(); + + /** + * Is this property allows to assign modified meta after exiting from {@link ItemProperty#apply} method. + * + * Return true if you make simple properties which modify only ItemMeta + * and doesn't touches ItemStack to assign it manually. + * + * If you set it for true and set item meta manually + * inside {@link ItemProperty#apply} method, then all changes won't be saved. + * @return true if allow or false otherwise + */ + boolean isApplyMeta(); + + /** + * Apply property to ItemStack. + * @param item Source ItemStack. + * @param meta Current meta of this item. + * @param player Player for who this item builds. + * @param menu Menu which cause item building. + */ + void apply(ItemStack item, ItemMeta meta, Player player, Menu menu); + +} diff --git a/api/src/main/java/ru/abstractmenus/api/inventory/Menu.java b/api/src/main/java/ru/abstractmenus/api/inventory/Menu.java new file mode 100644 index 0000000..26a4a41 --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/inventory/Menu.java @@ -0,0 +1,122 @@ +package ru.abstractmenus.api.inventory; + +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.InventoryHolder; +import ru.abstractmenus.api.Activator; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +/** + * Represents a menu + */ +public interface Menu extends InventoryHolder, Cloneable { + + /** + * Get activators if this menu + * @return List of menu activators + */ + List getActivators(); + + /** + * Get activator that caused menu opening. Will be empty, if menu opened without activator + * @return Activator in Optional wrapper + */ + Optional getActivatedBy(); + + /** + * Get activation context object. This might be event, block, entity, etc + * @return Activation context object + */ + Optional getContext(); + + /** + * Get item in specified slot. This method works with already displayed items + * @param slot Inventory slot index + * @return Item object or null if not any items in specified slot + */ + Item getItem(int slot); + + /** + * Get all menu items. + * @return Collection of menu items + */ + Collection getItems(); + + /** + * Get menu size + * @return Size of the menu + */ + int getSize(); + + /** + * Set size of the menu + * @param size Required menu size + */ + void setSize(int size); + + /** + * Open menu for required player. Do not use this method to open menu. Always use AbstractMenusPlugin instance (wee wiki) + * @param player Required player + * @return true if menu opened or false otherwise (for example, if player doesn't match rules) + */ + boolean open(Player player); + + /** + * Refresh items in the menu + * @param player Required player + */ + void refresh(Player player); + + /** + * Refresh item in specified inventory slot + * @param slot Item slot index + * @param player Menu owner + */ + void refreshItem(Slot slot, Player player); + + /** + * Temporary set item in current menu's inventory + * @param slot Inventory slot index + * @param item Required item + * @param player Menu owner + */ + void setItem(Slot slot, Item item, Player player); + + /** + * Update this menu. This method uses for auto updates by interval + * @param player Who opened menu + */ + void update(Player player); + + /** + * Close menu for player + * @param player Who opened menu + */ + default void close(Player player){ + close(player, true); + } + + /** + * Close menu for player + * @param player Who opened menu + * @param closeInventory Is inventory must be closed + */ + void close(Player player, boolean closeInventory); + + /** + * Process click on specified slot + * @param slot Clicked slot + * @param player Player who clicked + * @param type Click type enum + */ + void click(int slot, Player player, ClickType type); + + /** + * Make copy if this menu + * @return Copy of the menu + */ + Menu clone(); +} diff --git a/api/src/main/java/ru/abstractmenus/api/inventory/Slot.java b/api/src/main/java/ru/abstractmenus/api/inventory/Slot.java new file mode 100644 index 0000000..d8203d4 --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/inventory/Slot.java @@ -0,0 +1,18 @@ +package ru.abstractmenus.api.inventory; + +import java.util.function.Consumer; + +/** + * Inventory slot representation. + * Implementors of this interface returns one or multiple slots. + * For example, range slot type returns several slots between x-y inclusive + */ +public interface Slot { + + /** + * Get one or multiple slot indexes + * @param indexCb Callback for slot indexes + */ + void getSlots(Consumer indexCb); + +} diff --git a/api/src/main/java/ru/abstractmenus/api/inventory/slot/SlotIndex.java b/api/src/main/java/ru/abstractmenus/api/inventory/slot/SlotIndex.java new file mode 100644 index 0000000..309df45 --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/inventory/slot/SlotIndex.java @@ -0,0 +1,35 @@ +package ru.abstractmenus.api.inventory.slot; + +import ru.abstractmenus.api.inventory.Slot; + +import java.util.function.Consumer; + +/** + * Slot defined just by index + * # Config example + * slot: 0 + * slot: 51 + * slot: -1 (auto insert in first free slot) + */ +public class SlotIndex implements Slot { + + private final int slot; + + public SlotIndex(int slot) { + this.slot = slot; + } + + public int getIndex() { + return slot; + } + + @Override + public void getSlots(Consumer indexCb) { + indexCb.accept(slot); + } + + public static SlotIndex of(int index) { + return new SlotIndex(index); + } + +} diff --git a/api/src/main/java/ru/abstractmenus/api/inventory/slot/SlotMatrix.java b/api/src/main/java/ru/abstractmenus/api/inventory/slot/SlotMatrix.java new file mode 100644 index 0000000..013b542 --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/inventory/slot/SlotMatrix.java @@ -0,0 +1,31 @@ +package ru.abstractmenus.api.inventory.slot; + +import ru.abstractmenus.api.inventory.Slot; + +import java.util.function.Consumer; + +/** + * Slot defined by cells matrix + * # Config example + * slot: [ + * "xxxxxxxxx", + * "x-------x", + * "x-------x", + * "xxxxxxxxx", + * ] + */ +public class SlotMatrix implements Slot { + + private final Integer[] slots; + + public SlotMatrix(Integer[] slots) { + this.slots = slots; + } + + @Override + public void getSlots(Consumer indexCb) { + for (int slot : slots) { + indexCb.accept(slot); + } + } +} diff --git a/api/src/main/java/ru/abstractmenus/api/inventory/slot/SlotPos.java b/api/src/main/java/ru/abstractmenus/api/inventory/slot/SlotPos.java new file mode 100644 index 0000000..9800241 --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/inventory/slot/SlotPos.java @@ -0,0 +1,28 @@ +package ru.abstractmenus.api.inventory.slot; + +import ru.abstractmenus.api.inventory.Slot; + +import java.util.function.Consumer; + +/** + * Slot defined with x and y coordinates + * # Config example + * slot: "1, 3" + * slot { x: 1, y: 3 } + */ +public class SlotPos implements Slot { + + private final int x; + private final int y; + + public SlotPos(int x, int y) { + this.x = x; + this.y = y; + } + + @Override + public void getSlots(Consumer indexCb) { + indexCb.accept((y-1) * 9 + (x-1)); + } + +} diff --git a/api/src/main/java/ru/abstractmenus/api/inventory/slot/SlotRange.java b/api/src/main/java/ru/abstractmenus/api/inventory/slot/SlotRange.java new file mode 100644 index 0000000..5ab295f --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/inventory/slot/SlotRange.java @@ -0,0 +1,30 @@ +package ru.abstractmenus.api.inventory.slot; + +import ru.abstractmenus.api.inventory.Slot; + +import java.util.function.Consumer; + +/** + * Ranged slot defined with min and max indexes + * # Config example + * slot: "0-9" + * slot: "12-45" + */ +public class SlotRange implements Slot { + + private final int min; + private final int max; + + public SlotRange(int min, int max) { + this.min = Math.max(min, 0); + this.max = max; + } + + @Override + public void getSlots(Consumer indexCb) { + for (int i = min; i <= max; i++) { + indexCb.accept(i); + } + } + +} diff --git a/api/src/main/java/ru/abstractmenus/api/text/Colors.java b/api/src/main/java/ru/abstractmenus/api/text/Colors.java new file mode 100644 index 0000000..6ce3830 --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/text/Colors.java @@ -0,0 +1,107 @@ +package ru.abstractmenus.api.text; + +import org.bukkit.ChatColor; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Util to easy color codes replacing + */ +public class Colors { + + private static final char COLOR_PREFIX = '&'; + private static Replacer replacer; + + /** + * Initialize util. Do not call this method manually + * @param replaceRgb Replace RGB tags + */ + public static void init(boolean replaceRgb) { + if (isSupportRgb() && replaceRgb) { + replacer = new RgbReplacer(); + } else { + replacer = new SimpleReplacer(); + } + } + + /** + * Replace all color codes + * @param line Required single string + * @return String with replaced colors + */ + public static String of(String line) { + return replacer.replace(line); + } + + /** + * Replace all color codes + * @param list Required strings list + * @return Strings list with replaced colors + */ + public static List ofList(List list){ + for(int i = 0; i < list.size(); i++){ + list.set(i, of(list.get(i))); + } + + return list; + } + + /** + * Replace all color codes + * @param array Required strings array + * @return Strings array with replaced colors + */ + public static String[] ofArr(String[] array){ + for (int i = 0; i < array.length; i++){ + array[i] = of(array[i]); + } + return array; + } + + private static boolean isSupportRgb() { + try { + Class.forName("net.md_5.bungee.api.ChatColor") + .getDeclaredMethod("of", String.class); + return true; + } catch (Throwable t) { + return false; + } + } + + private interface Replacer { + String replace(String input); + } + + private static class SimpleReplacer implements Replacer { + + @Override + public String replace(String input) { + return ChatColor.translateAlternateColorCodes(COLOR_PREFIX, input); + } + } + + private static class RgbReplacer implements Replacer { + + private static final Pattern PATTERN = Pattern.compile("<#([A-Fa-f0-9]){6}>"); + + @Override + public String replace(String input) { + Matcher matcher = PATTERN.matcher(input); + + while (matcher.find()) { + String group = matcher.group(); + net.md_5.bungee.api.ChatColor hexColor = net.md_5.bungee.api.ChatColor.of(group + .substring(1, group.length() - 1)); + String before = input.substring(0, matcher.start()); + String after = input.substring(matcher.end()); + input = before + hexColor + after; + matcher = PATTERN.matcher(input); + } + + return ChatColor.translateAlternateColorCodes(COLOR_PREFIX, input); + } + } + +} diff --git a/api/src/main/java/ru/abstractmenus/api/variables/Var.java b/api/src/main/java/ru/abstractmenus/api/variables/Var.java new file mode 100644 index 0000000..adf1c48 --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/variables/Var.java @@ -0,0 +1,82 @@ +package ru.abstractmenus.api.variables; + +/** + * Variable data. Variables are immutable. + * If you need to change and save it again, you need to clone it first. + */ +public interface Var { + + /** + * Name of this variable + * @return Name of this variable + */ + String name(); + + /** + * Raw value of variable. Values always stored in string format + * @return Raw value of this variable + */ + String value(); + + /** + * Expiry time of this variable, if specified. + * This time can be compared, using System.currentTimeMillis() + * If variable has no expiry time, it will return 0 + * @return Expiry time + */ + long expiry(); + + /** + * Has this variable expiry time + * @return Try if variable has expiry time or false otherwise + */ + boolean hasExpiry(); + + /** + * Is this variable expired + * @return true if variable expired, false otherwise + */ + boolean isExpired(); + + /** + * Parse value as boolean + * Raw value will be parsed as true, if it equals to "true" or "1" + * @return Boolean value, parsed from raw string + */ + boolean boolValue(); + + /** + * Parse value as integer + * @return Integer value, parsed from raw string + * @throws NumberFormatException if raw value cannot be parsed as integer + */ + int intValue() throws NumberFormatException; + + /** + * Parse value as long + * @return Long value, parsed from raw string + * @throws NumberFormatException if raw value cannot be parsed as long + */ + long longValue() throws NumberFormatException; + + /** + * Parse value as float + * @return Float value, parsed from raw string + * @throws NumberFormatException if raw value cannot be parsed as float + */ + float floatValue() throws NumberFormatException; + + /** + * Parse value as double + * @return Double value, parsed from raw string + * @throws NumberFormatException if raw value cannot be parsed as double + */ + double doubleValue() throws NumberFormatException; + + /** + * Convert this value to builder with current values + * @return New builder with current var values + */ + VarBuilder toBuilder(); + +} diff --git a/api/src/main/java/ru/abstractmenus/api/variables/VarBuilder.java b/api/src/main/java/ru/abstractmenus/api/variables/VarBuilder.java new file mode 100644 index 0000000..7eb68a2 --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/variables/VarBuilder.java @@ -0,0 +1,54 @@ +package ru.abstractmenus.api.variables; + +/** + * Builder for variable + */ +public interface VarBuilder { + + /** + * Get current name + * @return Current name + */ + String name(); + + /** + * Get current value + * @return Current value + */ + String value(); + + /** + * Get current expiry time + * @return Current expiry time + */ + long expiry(); + + /** + * Set expiry time to variable + * @param name Variable name. Name should contain only Latin chars and special symbol `_` + * @return Builder instance + */ + VarBuilder name(String name); + + /** + * Set expiry time to variable + * @param value Variable value + * @return Builder instance + */ + VarBuilder value(String value); + + /** + * Set expiry time to variable + * @param expiry Expiry time in millis. Use System.currentTimeMillis() to + * get current time and add required millis to variable lifetime + * @return Builder instance + */ + VarBuilder expiry(long expiry); + + /** + * Build new variable + * @return New variable + */ + Var build(); + +} diff --git a/api/src/main/java/ru/abstractmenus/api/variables/VariableManager.java b/api/src/main/java/ru/abstractmenus/api/variables/VariableManager.java new file mode 100644 index 0000000..dc2b6d8 --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/variables/VariableManager.java @@ -0,0 +1,74 @@ +package ru.abstractmenus.api.variables; + +/** + * Manager for variables. Here you can use CRUD methods to manipulate with variables + */ +public interface VariableManager { + + /** + * Get global variable + * @param name Key of variable + * @return Found variable or null + */ + Var getGlobal(String name); + + /** + * Get personal variable + * @param username Owner if variable + * @param name Key of variable + * @return Found variable of null + */ + Var getPersonal(String username, String name); + + /** + * Save variable as global + * @param var Variable data + * @param replace If false, then stored variable won't be replaced + */ + void saveGlobal(Var var, boolean replace); + + /** + * Create or update variable as personal for some player + * @param var Variable data + */ + default void saveGlobal(Var var) { + saveGlobal(var, true); + } + + /** + * Save variable as personal for some player + * @param var Variable data + * @param username Variable owner + * @param replace If false, then stored variable won't be replaced + */ + void savePersonal(String username, Var var, boolean replace); + + /** + * Create or update variable as personal for some player + * @param var Variable data + * @param username Variable owner + */ + default void savePersonal(String username, Var var) { + savePersonal(username, var, true); + } + + /** + * Delete global variable by name + * @param name Name of variable + */ + void deleteGlobal(String name); + + /** + * Delete personal variable by name and owner + * @param username Variable owner + * @param name Name of variable + */ + void deletePersonal(String username, String name); + + /** + * Create builder for variable + * @return New variable builder + */ + VarBuilder createBuilder(); + +} From 27512a4840b5eb051efa26590031cb74aa6a1794 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Wed, 22 Apr 2026 17:44:11 +0500 Subject: [PATCH 07/71] feat(plugin): depend on :api subproject (drop external JitPack API dep) Co-Authored-By: Claude Opus 4.7 (1M context) --- plugin/build.gradle | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugin/build.gradle b/plugin/build.gradle index a858452..e106d97 100644 --- a/plugin/build.gradle +++ b/plugin/build.gradle @@ -42,8 +42,7 @@ dependencies { paperweight.paperDevBundle("1.21.11-R0.1-SNAPSHOT") - // NOTE: was 'com.github.AbstractMenus:api:995cd8c9a9' — swapped in Task 8 - implementation 'com.github.AbstractMenus:api:995cd8c9a9' + implementation project(':api') implementation 'com.fathzer:javaluator:3.0.6' implementation 'net.kyori:adventure-platform-bukkit:4.4.1' From f4917d04f9c8de4e3ef6c26c6631e63b5e85326e Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Wed, 22 Apr 2026 17:47:48 +0500 Subject: [PATCH 08/71] build(api): add maven-publish with GitHub Packages target Co-Authored-By: Claude Opus 4.7 (1M context) --- api/build.gradle | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/api/build.gradle b/api/build.gradle index 25fc8fc..cfca839 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -1,5 +1,6 @@ plugins { id 'java-library' + id 'maven-publish' } repositories { @@ -27,3 +28,38 @@ javadoc { title = "AbstractMenus ${project.version} API" options.addStringOption('Xdoclint:none', '-quiet') } + +publishing { + publications { + maven(MavenPublication) { + from components.java + // artifactId defaults to project.name == 'api' + // → Maven coord: ru.abstractmenus:api: + + pom { + name = 'AbstractMenus API' + description = 'Public API for the AbstractMenus Minecraft plugin (Paper/Folia).' + url = 'https://github.com/AbstractMenus/minecraft-plugin' + licenses { + license { + name = 'MIT License' + url = 'https://github.com/AbstractMenus/minecraft-plugin/blob/master/LICENSE' + } + } + scm { + url = 'https://github.com/AbstractMenus/minecraft-plugin' + } + } + } + } + repositories { + maven { + name = 'GitHubPackages' + url = uri('https://maven.pkg.github.com/AbstractMenus/minecraft-plugin') + credentials { + username = System.getenv('GITHUB_ACTOR') + password = System.getenv('GITHUB_TOKEN') + } + } + } +} From e7fb399b62e63729dedbbdb2908865a788c484a7 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Wed, 22 Apr 2026 17:51:28 +0500 Subject: [PATCH 09/71] ci: add jitpack.yml for multi-module api publishing Co-Authored-By: Claude Opus 4.7 (1M context) --- jitpack.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 jitpack.yml diff --git a/jitpack.yml b/jitpack.yml new file mode 100644 index 0000000..8e1a95a --- /dev/null +++ b/jitpack.yml @@ -0,0 +1,8 @@ +jdk: + - openjdk21 + +before_install: + - sdk install java 21.0.3-tem < /dev/null || true + +install: + - ./gradlew :api:publishToMavenLocal -x test From 72a85058e5dfeaf23f569cfb44683b3cf15df74c Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Wed, 22 Apr 2026 17:54:51 +0500 Subject: [PATCH 10/71] =?UTF-8?q?ci(build):=20support=20multi-module=20?= =?UTF-8?q?=E2=80=94=20upload=20api=20artifacts=20alongside=20plugin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/build.yml | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b7c52f0..08a19dc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -38,8 +38,8 @@ jobs: with: gradle-home-cache-cleanup: true - - name: Build + tests - run: ./gradlew --no-daemon build shadowJar + - name: Build + tests (all modules) + shadowJar + run: ./gradlew --no-daemon build :plugin:shadowJar - name: Publish test report if: always() @@ -49,14 +49,32 @@ jobs: fail_on_failure: true require_tests: false - - name: Resolve JAR path - id: jar - run: echo "path=$(ls build/libs/AbstractMenus-*.jar | head -n1)" >> "$GITHUB_OUTPUT" + - name: Resolve plugin JAR path + id: plugin_jar + run: echo "path=$(ls plugin/build/libs/AbstractMenus-*.jar | head -n1)" >> "$GITHUB_OUTPUT" - - name: Upload shaded JAR + - name: Resolve API JAR paths + id: api_jars + run: | + echo "binary=$(ls api/build/libs/api-*.jar | grep -v 'sources\|javadoc' | head -n1)" >> "$GITHUB_OUTPUT" + echo "sources=$(ls api/build/libs/api-*-sources.jar | head -n1)" >> "$GITHUB_OUTPUT" + echo "javadoc=$(ls api/build/libs/api-*-javadoc.jar | head -n1)" >> "$GITHUB_OUTPUT" + + - name: Upload plugin shadow JAR uses: actions/upload-artifact@v4 with: name: AbstractMenus-jar - path: ${{ steps.jar.outputs.path }} + path: ${{ steps.plugin_jar.outputs.path }} + if-no-files-found: error + retention-days: 14 + + - name: Upload api artifacts + uses: actions/upload-artifact@v4 + with: + name: AbstractMenus-api + path: | + ${{ steps.api_jars.outputs.binary }} + ${{ steps.api_jars.outputs.sources }} + ${{ steps.api_jars.outputs.javadoc }} if-no-files-found: error retention-days: 14 From 34635f5fe50ca8ae7a1a73279ef8d1200aaf8d75 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Wed, 22 Apr 2026 17:56:41 +0500 Subject: [PATCH 11/71] ci(release): attach api artifacts (binary+sources+javadoc) alongside plugin jar Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/release.yml | 42 +++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0a09a3b..d299f99 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,7 +6,7 @@ on: workflow_dispatch: inputs: tag: - description: 'Existing release tag to attach a rebuilt JAR to' + description: 'Existing release tag to attach rebuilt JARs to' required: true type: string @@ -15,7 +15,7 @@ permissions: jobs: attach-jar: - name: Build + attach JAR to release + name: Build + attach JARs to release runs-on: ubuntu-latest steps: - name: Checkout (release tag) @@ -36,30 +36,38 @@ jobs: - name: Verify tag matches project version run: | TAG="${{ github.event.release.tag_name || inputs.tag }}" - # Strip a leading 'v' if present (v1.18.0 → 1.18.0) TAG_VERSION="${TAG#v}" - GRADLE_VERSION=$(grep -E "^version\s+['\"]" build.gradle | sed -E "s/.*['\"]([^'\"]+)['\"].*/\1/") - echo "release tag: $TAG" - echo "tag version: $TAG_VERSION" - echo "gradle version: $GRADLE_VERSION" + GRADLE_VERSION=$(grep -E "^\s*version\s*=?\s*['\"]" build.gradle | head -n1 | sed -E "s/.*['\"]([^'\"]+)['\"].*/\1/") + echo "release tag: $TAG" + echo "tag version: $TAG_VERSION" + echo "gradle version: $GRADLE_VERSION" if [ "$TAG_VERSION" != "$GRADLE_VERSION" ]; then - echo "::error::Release tag version ($TAG_VERSION) does not match build.gradle version ($GRADLE_VERSION). Bump build.gradle before tagging, or retag." + echo "::error::Release tag version ($TAG_VERSION) does not match root build.gradle version ($GRADLE_VERSION). Bump build.gradle before tagging, or retag." exit 1 fi - - name: Build shaded JAR (skip tests — already run in Build workflow on the tagged commit) - run: ./gradlew --no-daemon shadowJar + - name: Build shadow plugin + api artifacts (skip tests — already run on the tagged commit by build.yml) + run: ./gradlew --no-daemon :plugin:shadowJar :api:build - - name: Resolve JAR path - id: jar + - name: Resolve artifact paths + id: paths run: | - JAR=$(ls build/libs/AbstractMenus-*.jar | head -n1) - echo "path=$JAR" >> "$GITHUB_OUTPUT" - echo "name=$(basename "$JAR")" >> "$GITHUB_OUTPUT" + PLUGIN=$(ls plugin/build/libs/AbstractMenus-*.jar | head -n1) + API_BIN=$(ls api/build/libs/api-*.jar | grep -v 'sources\|javadoc' | head -n1) + API_SRC=$(ls api/build/libs/api-*-sources.jar | head -n1) + API_DOC=$(ls api/build/libs/api-*-javadoc.jar | head -n1) + echo "plugin=$PLUGIN" >> "$GITHUB_OUTPUT" + echo "api_bin=$API_BIN" >> "$GITHUB_OUTPUT" + echo "api_src=$API_SRC" >> "$GITHUB_OUTPUT" + echo "api_doc=$API_DOC" >> "$GITHUB_OUTPUT" - - name: Attach JAR to release + - name: Attach JARs to release uses: softprops/action-gh-release@v2 with: tag_name: ${{ github.event.release.tag_name || inputs.tag }} - files: ${{ steps.jar.outputs.path }} + files: | + ${{ steps.paths.outputs.plugin }} + ${{ steps.paths.outputs.api_bin }} + ${{ steps.paths.outputs.api_src }} + ${{ steps.paths.outputs.api_doc }} fail_on_unmatched_files: true From d990f68fd5547b612e79352b6000b2d3eecbaee4 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Wed, 22 Apr 2026 17:57:57 +0500 Subject: [PATCH 12/71] ci: add publish-api workflow (GitHub Packages on release) Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/publish-api.yml | 40 +++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .github/workflows/publish-api.yml diff --git a/.github/workflows/publish-api.yml b/.github/workflows/publish-api.yml new file mode 100644 index 0000000..e3113f2 --- /dev/null +++ b/.github/workflows/publish-api.yml @@ -0,0 +1,40 @@ +name: Publish API to GitHub Packages + +on: + release: + types: [published] + workflow_dispatch: + inputs: + tag: + description: 'Release tag to re-publish' + required: true + type: string + +permissions: + contents: read + packages: write + +jobs: + publish: + name: Publish :api to GH Packages + runs-on: ubuntu-latest + steps: + - name: Checkout (release tag) + uses: actions/checkout@v4 + with: + ref: ${{ github.event.release.tag_name || inputs.tag }} + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Publish :api + run: ./gradlew --no-daemon :api:publish + env: + GITHUB_ACTOR: ${{ github.actor }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 80130aa0ed07f3c43edf7d150f78bc0ac90acf81 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Wed, 22 Apr 2026 17:59:44 +0500 Subject: [PATCH 13/71] release: bump to 2.0.0-alpha (first monorepo cut) Co-Authored-By: Claude Opus 4.7 (1M context) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 3b5ace6..7fdcbb7 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ allprojects { group = 'ru.abstractmenus' - version = '1.18.0' // bumped to 2.0.0-alpha in Task 13 + version = '2.0.0-alpha' repositories { mavenLocal() From 27916878f3ed919b6de026fb589d892d46951500 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Wed, 22 Apr 2026 19:29:05 +0500 Subject: [PATCH 14/71] docs(api): rich javadoc example on EconomyHandler Adds class-level overview, registration/impl/menu examples, threading notes, and per-method @implNote/@apiNote guidance. Style template for future API documentation passes. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../api/handler/EconomyHandler.java | 137 ++++++++++++++++-- 1 file changed, 125 insertions(+), 12 deletions(-) diff --git a/api/src/main/java/ru/abstractmenus/api/handler/EconomyHandler.java b/api/src/main/java/ru/abstractmenus/api/handler/EconomyHandler.java index ead566d..7a7d426 100644 --- a/api/src/main/java/ru/abstractmenus/api/handler/EconomyHandler.java +++ b/api/src/main/java/ru/abstractmenus/api/handler/EconomyHandler.java @@ -1,32 +1,145 @@ package ru.abstractmenus.api.handler; import org.bukkit.entity.Player; +import ru.abstractmenus.api.Handlers; /** - * Economy handler needs for economy actions and rules + * Handler for economy operations invoked by money-related menu actions and rules. + * + *

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

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

Registration

+ * + * A single handler is active at a time. Install yours in your plugin's + * {@code onEnable()}: + * + *
{@code
+ * @Override
+ * public void onEnable() {
+ *     Handlers.setEconomyHandler(new PlayerPointsEconomy(playerPointsApi));
+ * }
+ * }
+ * + *

Example — bridging PlayerPoints

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

Menu usage

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

Threading

+ * + * All methods are called on the main server thread in response to inventory + * events. Implementations may touch the Bukkit API directly and should avoid + * blocking IO; if the underlying economy plugin hits a database, cache the + * last known balance and refresh asynchronously. + * + * @see Handlers#setEconomyHandler(EconomyHandler) + * @see Handlers#getEconomyHandler() + * @see PermissionsHandler + * @see LevelHandler */ public interface EconomyHandler { /** - * Check is player has balance - * @param player Player to check - * @param balance Required balance - * @return true if player has required amount on balance + * Returns {@code true} if {@code player} has at least {@code balance} + * in this economy. Backs the {@code hasMoney} menu rule. + * + *

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

AbstractMenus does not re-check the balance before + * calling this method, even when the menu pairs the action with a + * {@code hasMoney} rule. Implementations that need to reject the + * withdrawal (e.g. the balance went negative between the check and the + * call) should fail silently — there is no way to cancel an + * in-flight action from the handler. + * + * @param player the player to charge; never {@code null}, always online + * @param amount the amount to withdraw; non-negative. Handlers bridging + * an integer currency (PlayerPoints, TokenManager) should + * round towards zero ({@code (int) amount}) to match + * {@link #giveBalance(Player, double)}. */ void takeBalance(Player player, double amount); /** - * Give some amount to player's balance - * @param player Required player - * @param amount Required amount + * Adds {@code amount} to {@code player}'s balance. Backs the + * {@code giveMoney} menu action. + * + * @param player the player to credit; never {@code null}, always online + * @param amount the amount to deposit; non-negative + * + * @implNote Handlers bridging an integer currency should round + * {@code amount} towards zero, matching + * {@link #takeBalance(Player, double)}. */ void giveBalance(Player player, double amount); - } From 6a12a619c38c2cb7b54e7104e3d618e4aad80265 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Wed, 22 Apr 2026 20:20:38 +0500 Subject: [PATCH 15/71] =?UTF-8?q?feat(api):=20add=20MenuExtension=20interf?= =?UTF-8?q?ace=20=E2=80=94=20addon=20contract?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Intermediate broken build state: references AbstractMenusApi which is added in the next commit. api module re-compiles cleanly after Task 3. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../ru/abstractmenus/api/MenuExtension.java | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 api/src/main/java/ru/abstractmenus/api/MenuExtension.java diff --git a/api/src/main/java/ru/abstractmenus/api/MenuExtension.java b/api/src/main/java/ru/abstractmenus/api/MenuExtension.java new file mode 100644 index 0000000..eb410b4 --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/MenuExtension.java @@ -0,0 +1,117 @@ +package ru.abstractmenus.api; + +/** + * Contract an AbstractMenus addon implements to register custom types + * (actions, rules, item properties, activators, catalogs) at runtime. + * + *

Two paths for delivery: + * + *

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

Lifecycle

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

Thread safety

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

Registry entries are cleaned up automatically — you only need + * to unregister Bukkit listeners, cancel scheduler tasks, and close any + * resources you opened yourself. + * + * @param api the running AbstractMenus API + */ + default void onDisable(AbstractMenusApi api) { + } + + /** + * Display name. Used by {@code /am addons list} and log messages. May + * return {@code null}; for AM-loaded addons (Path 2) the name from + * {@code addon.conf} is used as a fallback. + * + * @return the extension display name, or {@code null} to defer + */ + default String name() { + return null; + } + + /** + * Extension version string. Used for diagnostics. May return {@code null} + * — Path 2 falls back to the {@code version} from + * {@code addon.conf}. + * + * @return the version, or {@code null} to defer + */ + default String version() { + return null; + } + + /** + * The AbstractMenus API version this extension was built against. Purely + * diagnostic — logged on load and shown in {@code /am addons info}. + * No compatibility enforcement is performed; if the current API is + * incompatible, the extension will fail naturally on {@code onEnable} + * with a clear stacktrace. + * + * @return the target API version string (e.g. {@code "2.0.0"}), or + * {@code null} if not tracked + */ + default String targetApiVersion() { + return null; + } +} From b5d1fb4b8f37c4f5a812a1e50d80c7383d5a3faf Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Wed, 22 Apr 2026 20:22:17 +0500 Subject: [PATCH 16/71] =?UTF-8?q?feat(api):=20add=20TypeRegistry=20?= =?UTF-8?q?=E2=80=94=20owner-aware=20replacement=20for=20Types.*=20statics?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Build still fails pending AbstractMenusApi (next task). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../ru/abstractmenus/api/TypeRegistry.java | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 api/src/main/java/ru/abstractmenus/api/TypeRegistry.java diff --git a/api/src/main/java/ru/abstractmenus/api/TypeRegistry.java b/api/src/main/java/ru/abstractmenus/api/TypeRegistry.java new file mode 100644 index 0000000..ecee338 --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/TypeRegistry.java @@ -0,0 +1,103 @@ +package ru.abstractmenus.api; + +import ru.abstractmenus.hocon.api.serialize.NodeSerializer; + +import java.util.Set; + +/** + * Typed registry of menu type classes keyed by a HOCON-visible name. + * + *

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

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

Example

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

Threading

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

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

The {@code serializer} is also registered against the shared + * {@link ru.abstractmenus.hocon.api.serialize.NodeSerializer NodeSerializer} + * collection so HOCON parsing can deserialize instances of this type. + * + * @param the concrete subtype + * @param key HOCON-visible name (case-insensitive) + * @param type the class token; must be assignable to {@code T} + * @param serializer HOCON serializer for {@code type} + * @param owner the registering extension; used for later + * {@link #unregisterAll(MenuExtension)} cleanup. Pass + * the core extension for core registrations. + */ + void register(String key, + Class type, + NodeSerializer serializer, + MenuExtension owner); + + /** + * Look up a registered subtype by key. + * + * @param key HOCON-visible name (case-insensitive) + * @return the registered class, or {@code null} if not found + */ + Class get(String key); + + /** + * Reverse lookup — the HOCON name a given class is registered under. + * + * @param type the class to look up + * @return the registered key, or {@code null} if not registered + */ + String name(Class type); + + /** + * All registered keys. Returned set is an unmodifiable snapshot. + * + * @return all registered keys (lowercased) + */ + Set keys(); + + /** + * Remove every entry registered by {@code owner}. Invoked by the addon + * manager when an extension is disabled or reloaded. + * + * @param owner the extension whose entries should be wiped + */ + void unregisterAll(MenuExtension owner); +} From 5425272ce61bb6dee9b55d862a837b30c884f0b6 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Wed, 22 Apr 2026 20:24:14 +0500 Subject: [PATCH 17/71] =?UTF-8?q?feat(api):=20add=20AbstractMenusApi=20?= =?UTF-8?q?=E2=80=94=20entry=20interface=20(replaces=20AbstractMenusPlugin?= =?UTF-8?q?=20+=20Types=20accessors)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolves the intermediate broken build state from MenuExtension + TypeRegistry commits. :api now compiles cleanly. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../abstractmenus/api/AbstractMenusApi.java | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 api/src/main/java/ru/abstractmenus/api/AbstractMenusApi.java diff --git a/api/src/main/java/ru/abstractmenus/api/AbstractMenusApi.java b/api/src/main/java/ru/abstractmenus/api/AbstractMenusApi.java new file mode 100644 index 0000000..39f2e63 --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/AbstractMenusApi.java @@ -0,0 +1,132 @@ +package ru.abstractmenus.api; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import ru.abstractmenus.api.inventory.ItemProperty; +import ru.abstractmenus.api.inventory.Menu; +import ru.abstractmenus.api.variables.VariableManager; +import ru.abstractmenus.hocon.api.serialize.NodeSerializers; + +import java.util.Optional; + +/** + * Runtime entry point for AbstractMenus. Exposes the type registries, the + * shared HOCON {@link NodeSerializers}, menu lifecycle operations, and the + * variable manager. + * + *

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

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

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

getOpenedMenu(Player player); + + // ---- Meta ----------------------------------------------------------- + + /** + * API version string, taken from the plugin's {@code version}. Purely + * diagnostic — logged on startup and surfaced in + * {@code /am addons list}. + * + * @return the API version (e.g. {@code "2.0.0-alpha"}) + */ + String apiVersion(); +} From 14698fd288659f36cfe1a0aa241c36826f5c4aa4 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Wed, 22 Apr 2026 20:48:25 +0500 Subject: [PATCH 18/71] feat(plugin): TypeRegistryImpl with owner-tracked register/unregister Co-Authored-By: Claude Opus 4.7 (1M context) --- .../abstractmenus/api/TypeRegistryImpl.java | 100 +++++++++++++++ .../api/TypeRegistryImplTest.java | 115 ++++++++++++++++++ 2 files changed, 215 insertions(+) create mode 100644 plugin/src/main/java/ru/abstractmenus/api/TypeRegistryImpl.java create mode 100644 plugin/src/test/java/ru/abstractmenus/api/TypeRegistryImplTest.java diff --git a/plugin/src/main/java/ru/abstractmenus/api/TypeRegistryImpl.java b/plugin/src/main/java/ru/abstractmenus/api/TypeRegistryImpl.java new file mode 100644 index 0000000..225e081 --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/api/TypeRegistryImpl.java @@ -0,0 +1,100 @@ +package ru.abstractmenus.api; + +import ru.abstractmenus.hocon.api.serialize.NodeSerializer; +import ru.abstractmenus.hocon.api.serialize.NodeSerializers; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Set; +import java.util.logging.Logger; + +/** + * In-memory {@link TypeRegistry} implementation. Not thread-safe for + * concurrent registration; consumers must register/unregister from the main + * server thread. + * + *

Note on {@code NodeSerializers.unregister}: hocon 1.0.6 does NOT expose + * an {@code unregister(Class)} method. Therefore stale serializer entries + * survive in {@link NodeSerializers} after {@link #unregisterAll(MenuExtension)}, + * but that is harmless — the {@link #byKey} map is the authoritative lookup + * table, and a subsequent {@link #register} call for the same class token + * overwrites the serializer entry. + */ +public final class TypeRegistryImpl implements TypeRegistry { + + private static final Logger LOG = Logger.getLogger(TypeRegistryImpl.class.getName()); + + private final NodeSerializers serializers; + + /** key (lowercased) → registered class */ + private final Map> byKey = new HashMap<>(); + + /** class → key (reverse index, kept in sync with {@link #byKey}) */ + private final Map, String> byType = new IdentityHashMap<>(); + + /** owner → set of keys they registered (for unregisterAll) */ + private final Map> keysByOwner = new IdentityHashMap<>(); + + public TypeRegistryImpl(NodeSerializers serializers) { + this.serializers = serializers; + } + + @Override + public synchronized void register(String key, + Class type, + NodeSerializer serializer, + MenuExtension owner) { + String k = key.toLowerCase(); + + Class existing = byKey.get(k); + if (existing != null) { + LOG.warning("TypeRegistry: overwriting existing entry '" + k + + "' (" + existing.getName() + " -> " + type.getName() + ")"); + byType.remove(existing); + // Intentionally do not remove from owner tracking — the old owner + // no longer has this key since it's overwritten; cleanup of their + // orphan entries happens on their own unregisterAll. + } + + byKey.put(k, type); + byType.put(type, k); + serializers.register(type, serializer); + + keysByOwner.computeIfAbsent(owner, o -> new HashSet<>()).add(k); + } + + @Override + public synchronized Class get(String key) { + return byKey.get(key.toLowerCase()); + } + + @Override + public synchronized String name(Class type) { + return byType.get(type); + } + + @Override + public synchronized Set keys() { + return Collections.unmodifiableSet(new HashSet<>(byKey.keySet())); + } + + @Override + public synchronized void unregisterAll(MenuExtension owner) { + Set keys = keysByOwner.remove(owner); + if (keys == null) return; + + for (String k : keys) { + Class type = byKey.remove(k); + if (type != null) { + byType.remove(type); + // NodeSerializers.unregister(Class) does not exist in hocon 1.0.6. + // The stale serializer entry in NodeSerializers is harmless — + // byKey is authoritative, and re-registration overwrites it. + // serializers.unregister(type); + } + } + } +} diff --git a/plugin/src/test/java/ru/abstractmenus/api/TypeRegistryImplTest.java b/plugin/src/test/java/ru/abstractmenus/api/TypeRegistryImplTest.java new file mode 100644 index 0000000..a422010 --- /dev/null +++ b/plugin/src/test/java/ru/abstractmenus/api/TypeRegistryImplTest.java @@ -0,0 +1,115 @@ +package ru.abstractmenus.api; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import ru.abstractmenus.hocon.api.ConfigNode; +import ru.abstractmenus.hocon.api.serialize.NodeSerializer; +import ru.abstractmenus.hocon.api.serialize.NodeSerializers; +import ru.abstractmenus.hocon.api.serialize.NodeSerializeException; + +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; + +class TypeRegistryImplTest { + + interface Widget { } + static class RedWidget implements Widget { } + static class BlueWidget implements Widget { } + + private NodeSerializers serializers; + private TypeRegistryImpl registry; + private MenuExtension ownerA; + private MenuExtension ownerB; + + @BeforeEach + void setUp() { + serializers = NodeSerializers.defaults(); + registry = new TypeRegistryImpl<>(serializers); + ownerA = new DummyExtension("A"); + ownerB = new DummyExtension("B"); + } + + @Test + void register_exposesKey() { + NodeSerializer ser = dummySerializer(); + registry.register("red", RedWidget.class, ser, ownerA); + + assertEquals(RedWidget.class, registry.get("red")); + assertEquals("red", registry.name(RedWidget.class)); + assertEquals(Set.of("red"), registry.keys()); + } + + @Test + void register_isCaseInsensitive() { + NodeSerializer ser = dummySerializer(); + registry.register("Red", RedWidget.class, ser, ownerA); + + assertEquals(RedWidget.class, registry.get("RED")); + assertEquals(RedWidget.class, registry.get("red")); + assertEquals("red", registry.name(RedWidget.class)); + } + + @Test + void get_returnsNullForUnknown() { + assertNull(registry.get("nothing")); + } + + @Test + void unregisterAll_removesOnlyThatOwnersEntries() { + registry.register("red", RedWidget.class, dummySerializer(), ownerA); + registry.register("blue", BlueWidget.class, dummySerializer(), ownerB); + + registry.unregisterAll(ownerA); + + assertNull(registry.get("red")); + assertEquals(BlueWidget.class, registry.get("blue")); + assertEquals(Set.of("blue"), registry.keys()); + } + + @Test + void register_overwrite_replaces() { + registry.register("red", RedWidget.class, dummySerializer(), ownerA); + registry.register("red", BlueWidget.class, dummySerializer(), ownerB); + + assertEquals(BlueWidget.class, registry.get("red")); + assertNull(registry.name(RedWidget.class)); + } + + @Test + void unregisterAll_withNoRegistrations_isSafe() { + // Should not throw even if the owner has no entries. + registry.unregisterAll(ownerA); + assertEquals(Set.of(), registry.keys()); + } + + @Test + void keys_returnsLowercased() { + registry.register("RedWidget", RedWidget.class, dummySerializer(), ownerA); + assertEquals(Set.of("redwidget"), registry.keys()); + } + + // --- helpers --- + + /** + * NodeSerializer has one abstract method: + * T deserialize(Class, ConfigNode) throws NodeSerializeException + * Stubs return null (safe for tests that never actually deserialize). + */ + @SuppressWarnings("unchecked") + private NodeSerializer dummySerializer() { + return new NodeSerializer() { + @Override + public T deserialize(Class type, ConfigNode node) throws NodeSerializeException { + return null; + } + }; + } + + static class DummyExtension implements MenuExtension { + private final String name; + DummyExtension(String name) { this.name = name; } + @Override public void onEnable(AbstractMenusApi api) { } + @Override public String name() { return name; } + } +} From fa0194f4fd8694637551cbecdb72ac9d0b38a56d Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Wed, 22 Apr 2026 20:50:26 +0500 Subject: [PATCH 19/71] =?UTF-8?q?feat(plugin):=20AbstractMenusApiImpl=20?= =?UTF-8?q?=E2=80=94=20runtime=20holder=20of=20registries=20+=20plugin=20b?= =?UTF-8?q?ridge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 (1M context) --- .../api/AbstractMenusApiImpl.java | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 plugin/src/main/java/ru/abstractmenus/api/AbstractMenusApiImpl.java diff --git a/plugin/src/main/java/ru/abstractmenus/api/AbstractMenusApiImpl.java b/plugin/src/main/java/ru/abstractmenus/api/AbstractMenusApiImpl.java new file mode 100644 index 0000000..c1f2209 --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/api/AbstractMenusApiImpl.java @@ -0,0 +1,77 @@ +package ru.abstractmenus.api; + +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import ru.abstractmenus.AbstractMenus; +import ru.abstractmenus.api.inventory.ItemProperty; +import ru.abstractmenus.api.inventory.Menu; +import ru.abstractmenus.api.variables.VariableManager; +import ru.abstractmenus.hocon.api.serialize.NodeSerializers; + +import java.util.Optional; + +/** + * Main implementation of {@link AbstractMenusApi}. Instantiated once during + * plugin enable; published to Bukkit's ServicesManager so + * {@link AbstractMenusApi#get()} resolves it. + */ +public final class AbstractMenusApiImpl implements AbstractMenusApi { + + private final AbstractMenus plugin; + private final NodeSerializers serializers = NodeSerializers.defaults(); + + private final TypeRegistry actions; + private final TypeRegistry rules; + private final TypeRegistry activators; + private final TypeRegistry itemProperties; + private final TypeRegistry> catalogs; + + public AbstractMenusApiImpl(AbstractMenus plugin) { + this.plugin = plugin; + this.actions = new TypeRegistryImpl<>(serializers); + this.rules = new TypeRegistryImpl<>(serializers); + this.activators = new TypeRegistryImpl<>(serializers); + this.itemProperties = new TypeRegistryImpl<>(serializers); + this.catalogs = new TypeRegistryImpl<>(serializers); + } + + @Override public TypeRegistry actions() { return actions; } + @Override public TypeRegistry rules() { return rules; } + @Override public TypeRegistry activators() { return activators; } + @Override public TypeRegistry itemProperties() { return itemProperties; } + @Override public TypeRegistry> catalogs() { return catalogs; } + + @Override public NodeSerializers serializers() { return serializers; } + + @Override public VariableManager variables() { return plugin.getVariableManager(); } + + @Override public Plugin getPlugin() { return plugin; } + + @Override public void loadMenus() { plugin.loadMenus(); } + + @Override + public void openMenu(Activator activator, Object ctx, Player player, Menu menu) { + plugin.openMenu(activator, ctx, player, menu); + } + + @Override + public void openMenu(Player player, Menu menu) { + plugin.openMenu(player, menu); + } + + @Override + public Optional

getOpenedMenu(Player player) { + return plugin.getOpenedMenu(player); + } + + @Override + public String apiVersion() { + // Paper 1.21+ exposes getPluginMeta().getVersion(); fall back to + // getDescription().getVersion() if the new API isn't present. + try { + return plugin.getPluginMeta().getVersion(); + } catch (NoSuchMethodError | AbstractMethodError ignored) { + return plugin.getDescription().getVersion(); + } + } +} From ae307368821489d29eee9a7e2ff20a05a791c205 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Wed, 22 Apr 2026 20:52:54 +0500 Subject: [PATCH 20/71] feat(plugin): instantiate AbstractMenusApiImpl in onEnable + publish via ServicesManager Old Types.*.init() registration path continues unchanged. The new api field is just sitting published; no caller yet. Cutover in the CoreExtension task. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../main/java/ru/abstractmenus/AbstractMenus.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java b/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java index b591796..6c92ed8 100644 --- a/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java +++ b/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java @@ -13,6 +13,8 @@ import org.bukkit.plugin.ServicePriority; import org.bukkit.plugin.java.JavaPlugin; import ru.abstractmenus.api.*; +import ru.abstractmenus.api.AbstractMenusApi; +import ru.abstractmenus.api.AbstractMenusApiImpl; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.text.Colors; import ru.abstractmenus.api.variables.VariableManager; @@ -69,6 +71,7 @@ public final class AbstractMenus extends JavaPlugin implements AbstractMenusPlug private CommandManager commandManager; private Metrics metrics; private FoliaLib foliaLib; + private AbstractMenusApi api; @Getter @Setter @@ -79,6 +82,8 @@ public Plugin getPlugin() { return this; } + public AbstractMenusApi getApi() { return api; } + @Override public VariableManager getVariableManager() { return VariableManagerImpl.instance(); @@ -135,6 +140,13 @@ public void onEnable() { new MenuManager(this, config); + // Publish the new Extension API to Bukkit's ServicesManager so + // plugin-as-addons (Path 1) and the dogfood CoreExtension (wired in + // the next phase) can look it up via AbstractMenusApi.get(). + this.api = new AbstractMenusApiImpl(this); + getServer().getServicesManager().register( + AbstractMenusApi.class, api, this, ServicePriority.Normal); + registerProviders(); registerCommands(config); @@ -197,6 +209,7 @@ public void onDisable() { getServer().getMessenger().unregisterIncomingPluginChannel(this, "BungeeCord"); getServer().getMessenger().unregisterOutgoingPluginChannel(this, "BungeeCord"); + getServer().getServicesManager().unregisterAll(this); } private void registerCommands(MainConfig config) { From 112360d409931d971cdb4718f6db096981cf2fd7 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Wed, 22 Apr 2026 20:54:51 +0500 Subject: [PATCH 21/71] feat(plugin): CoreExtension + five empty bundle stubs (dogfood scaffold) Co-Authored-By: Claude Opus 4.7 (1M context) --- .../abstractmenus/core/CoreActionsBundle.java | 11 ++++++ .../core/CoreActivatorsBundle.java | 11 ++++++ .../core/CoreCatalogsBundle.java | 11 ++++++ .../ru/abstractmenus/core/CoreExtension.java | 37 +++++++++++++++++++ .../core/CoreItemPropsBundle.java | 11 ++++++ .../abstractmenus/core/CoreRulesBundle.java | 11 ++++++ 6 files changed, 92 insertions(+) create mode 100644 plugin/src/main/java/ru/abstractmenus/core/CoreActionsBundle.java create mode 100644 plugin/src/main/java/ru/abstractmenus/core/CoreActivatorsBundle.java create mode 100644 plugin/src/main/java/ru/abstractmenus/core/CoreCatalogsBundle.java create mode 100644 plugin/src/main/java/ru/abstractmenus/core/CoreExtension.java create mode 100644 plugin/src/main/java/ru/abstractmenus/core/CoreItemPropsBundle.java create mode 100644 plugin/src/main/java/ru/abstractmenus/core/CoreRulesBundle.java diff --git a/plugin/src/main/java/ru/abstractmenus/core/CoreActionsBundle.java b/plugin/src/main/java/ru/abstractmenus/core/CoreActionsBundle.java new file mode 100644 index 0000000..c9d24f7 --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/core/CoreActionsBundle.java @@ -0,0 +1,11 @@ +package ru.abstractmenus.core; + +import ru.abstractmenus.api.AbstractMenusApi; +import ru.abstractmenus.api.MenuExtension; + +/** Stub — populated in the MenuActions → bundle migration task. */ +final class CoreActionsBundle { + void register(AbstractMenusApi api, MenuExtension owner) { + // intentionally empty — content migrates from MenuActions.init() + } +} diff --git a/plugin/src/main/java/ru/abstractmenus/core/CoreActivatorsBundle.java b/plugin/src/main/java/ru/abstractmenus/core/CoreActivatorsBundle.java new file mode 100644 index 0000000..aa70796 --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/core/CoreActivatorsBundle.java @@ -0,0 +1,11 @@ +package ru.abstractmenus.core; + +import ru.abstractmenus.api.AbstractMenusApi; +import ru.abstractmenus.api.MenuExtension; + +/** Stub — populated in the Activators → bundle migration task. */ +final class CoreActivatorsBundle { + void register(AbstractMenusApi api, MenuExtension owner) { + // intentionally empty — content migrates from Activators.init() + } +} diff --git a/plugin/src/main/java/ru/abstractmenus/core/CoreCatalogsBundle.java b/plugin/src/main/java/ru/abstractmenus/core/CoreCatalogsBundle.java new file mode 100644 index 0000000..6cb4aff --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/core/CoreCatalogsBundle.java @@ -0,0 +1,11 @@ +package ru.abstractmenus.core; + +import ru.abstractmenus.api.AbstractMenusApi; +import ru.abstractmenus.api.MenuExtension; + +/** Stub — populated in the Catalogs → bundle migration task. */ +final class CoreCatalogsBundle { + void register(AbstractMenusApi api, MenuExtension owner) { + // intentionally empty — content migrates from Catalogs.init() + } +} diff --git a/plugin/src/main/java/ru/abstractmenus/core/CoreExtension.java b/plugin/src/main/java/ru/abstractmenus/core/CoreExtension.java new file mode 100644 index 0000000..42cc294 --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/core/CoreExtension.java @@ -0,0 +1,37 @@ +package ru.abstractmenus.core; + +import ru.abstractmenus.api.AbstractMenusApi; +import ru.abstractmenus.api.MenuExtension; + +/** + * The built-in "extension" that registers every core action, rule, item + * property, activator and catalog. Lives inside the plugin itself (not + * loaded via the addon classloader) and acts as the reference example of + * how external addons should structure their registration. + * + *

The five bundles keep registration grouped by surface area for + * readability. Each is a pure function of {@code api} and the owning + * {@code CoreExtension} instance — no static state. + */ +public final class CoreExtension implements MenuExtension { + + @Override + public String name() { + return "AbstractMenus-Core"; + } + + @Override + public String version() { + // Populated from plugin version at runtime via api.apiVersion(). + return null; + } + + @Override + public void onEnable(AbstractMenusApi api) { + new CoreActionsBundle().register(api, this); + new CoreRulesBundle().register(api, this); + new CoreItemPropsBundle().register(api, this); + new CoreActivatorsBundle().register(api, this); + new CoreCatalogsBundle().register(api, this); + } +} diff --git a/plugin/src/main/java/ru/abstractmenus/core/CoreItemPropsBundle.java b/plugin/src/main/java/ru/abstractmenus/core/CoreItemPropsBundle.java new file mode 100644 index 0000000..c71b48f --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/core/CoreItemPropsBundle.java @@ -0,0 +1,11 @@ +package ru.abstractmenus.core; + +import ru.abstractmenus.api.AbstractMenusApi; +import ru.abstractmenus.api.MenuExtension; + +/** Stub — populated in the ItemProps → bundle migration task. */ +final class CoreItemPropsBundle { + void register(AbstractMenusApi api, MenuExtension owner) { + // intentionally empty — content migrates from ItemProps.init() + } +} diff --git a/plugin/src/main/java/ru/abstractmenus/core/CoreRulesBundle.java b/plugin/src/main/java/ru/abstractmenus/core/CoreRulesBundle.java new file mode 100644 index 0000000..2a23ce3 --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/core/CoreRulesBundle.java @@ -0,0 +1,11 @@ +package ru.abstractmenus.core; + +import ru.abstractmenus.api.AbstractMenusApi; +import ru.abstractmenus.api.MenuExtension; + +/** Stub — populated in the MenuRules → bundle migration task. */ +final class CoreRulesBundle { + void register(AbstractMenusApi api, MenuExtension owner) { + // intentionally empty — content migrates from MenuRules.init() + } +} From 1e0aa69eebbfe707535695855b711cbf911e6474 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Wed, 22 Apr 2026 20:57:08 +0500 Subject: [PATCH 22/71] feat(core): populate CoreActionsBundle from MenuActions.init (not wired yet) Co-Authored-By: Claude Opus 4.7 (1M context) --- .../abstractmenus/core/CoreActionsBundle.java | 137 +++++++++++++++++- 1 file changed, 135 insertions(+), 2 deletions(-) diff --git a/plugin/src/main/java/ru/abstractmenus/core/CoreActionsBundle.java b/plugin/src/main/java/ru/abstractmenus/core/CoreActionsBundle.java index c9d24f7..f604a43 100644 --- a/plugin/src/main/java/ru/abstractmenus/core/CoreActionsBundle.java +++ b/plugin/src/main/java/ru/abstractmenus/core/CoreActionsBundle.java @@ -2,10 +2,143 @@ import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.MenuExtension; +import ru.abstractmenus.data.actions.var.*; +import ru.abstractmenus.data.actions.varp.*; +import ru.abstractmenus.data.actions.wrappers.ActionBulk; +import ru.abstractmenus.data.actions.wrappers.ActionDelay; +import ru.abstractmenus.data.actions.wrappers.ActionPlayerScope; +import ru.abstractmenus.data.actions.wrappers.ActionRandomActions; +import ru.abstractmenus.data.actions.ActionMenuClose; +import ru.abstractmenus.data.actions.ActionMenuOpen; +import ru.abstractmenus.data.actions.ActionMenuOpenCtx; +import ru.abstractmenus.data.actions.ActionGroupAdd; +import ru.abstractmenus.data.actions.ActionBungeeConnect; +import ru.abstractmenus.data.actions.ActionMenuRefresh; +import ru.abstractmenus.data.actions.ActionCommand; +import ru.abstractmenus.data.actions.ActionLevelGive; +import ru.abstractmenus.data.actions.ActionMoneyGive; +import ru.abstractmenus.data.actions.ActionPermissionGive; +import ru.abstractmenus.data.actions.ActionLuckPermsMetaSet; +import ru.abstractmenus.data.actions.ActionLuckPermsMetaRemove; +import ru.abstractmenus.data.actions.ActionXpGive; +import ru.abstractmenus.data.actions.ActionItemAdd; +import ru.abstractmenus.data.actions.ActionItemRemove; +import ru.abstractmenus.data.actions.ActionItemClear; +import ru.abstractmenus.data.actions.ActionInventoryClear; +import ru.abstractmenus.data.actions.ActionMessage; +import ru.abstractmenus.data.actions.ActionBroadcast; +import ru.abstractmenus.data.actions.ActionMiniMessage; +import ru.abstractmenus.data.actions.ActionPotionEffect; +import ru.abstractmenus.data.actions.ActionPotionEffectRemove; +import ru.abstractmenus.data.actions.ActionGroupRemove; +import ru.abstractmenus.data.actions.ActionPermissionRemove; +import ru.abstractmenus.data.actions.ActionFoodLevelSet; +import ru.abstractmenus.data.actions.ActionHealthSet; +import ru.abstractmenus.data.actions.ActionSound; +import ru.abstractmenus.data.actions.ActionSoundCustom; +import ru.abstractmenus.data.actions.ActionLevelTake; +import ru.abstractmenus.data.actions.ActionMoneyTake; +import ru.abstractmenus.data.actions.ActionXpTake; +import ru.abstractmenus.data.actions.ActionTeleport; +import ru.abstractmenus.data.actions.ActionBookOpen; +import ru.abstractmenus.data.actions.ActionSkinSet; +import ru.abstractmenus.data.actions.ActionSkinReset; +import ru.abstractmenus.data.actions.ActionRecipeAdd; +import ru.abstractmenus.data.actions.ActionPageNext; +import ru.abstractmenus.data.actions.ActionPagePrev; +import ru.abstractmenus.data.actions.ActionPropertySet; +import ru.abstractmenus.data.actions.ActionPropertyRemove; +import ru.abstractmenus.data.actions.ActionItemRefresh; +import ru.abstractmenus.data.actions.ActionPlayerChat; +import ru.abstractmenus.data.actions.ActionGameModeSet; +import ru.abstractmenus.data.actions.ActionInputChat; +import ru.abstractmenus.data.actions.ActionButtonSet; +import ru.abstractmenus.data.actions.ActionButtonRemove; +import ru.abstractmenus.data.actions.ActionPlacedItemRemove; +import ru.abstractmenus.data.actions.ActionPlaceItem; +import ru.abstractmenus.data.actions.ActionLog; -/** Stub — populated in the MenuActions → bundle migration task. */ +/** + * Core action registrations. Mirrors {@code MenuActions.init()} before the + * SPI refactor. + */ final class CoreActionsBundle { + void register(AbstractMenusApi api, MenuExtension owner) { - // intentionally empty — content migrates from MenuActions.init() + api.actions().register("closeMenu", ActionMenuClose.class, new ActionMenuClose.Serializer(), owner); + api.actions().register("openMenu", ActionMenuOpen.class, new ActionMenuOpen.Serializer(), owner); + api.actions().register("openMenuCtx", ActionMenuOpenCtx.class, new ActionMenuOpenCtx.Serializer(), owner); + api.actions().register("playerScope", ActionPlayerScope.class, new ActionPlayerScope.Serializer(), owner); + + api.actions().register("addGroup", ActionGroupAdd.class, new ActionGroupAdd.Serializer(), owner); + api.actions().register("bungeeConnect", ActionBungeeConnect.class, new ActionBungeeConnect.Serializer(), owner); + api.actions().register("refreshMenu", ActionMenuRefresh.class, new ActionMenuRefresh.Serializer(), owner); + api.actions().register("command", ActionCommand.class, new ActionCommand.Serializer(), owner); + api.actions().register("giveLevel", ActionLevelGive.class, new ActionLevelGive.Serializer(), owner); + api.actions().register("giveMoney", ActionMoneyGive.class, new ActionMoneyGive.Serializer(), owner); + api.actions().register("givePermission", ActionPermissionGive.class, new ActionPermissionGive.Serializer(), owner); + api.actions().register("lpMetaSet", ActionLuckPermsMetaSet.class, new ActionLuckPermsMetaSet.Serializer(), owner); + api.actions().register("lpMetaRemove", ActionLuckPermsMetaRemove.class, new ActionLuckPermsMetaRemove.Serializer(), owner); + api.actions().register("giveXp", ActionXpGive.class, new ActionXpGive.Serializer(), owner); + api.actions().register("itemAdd", ActionItemAdd.class, new ActionItemAdd.Serializer(), owner); + api.actions().register("itemRemove", ActionItemRemove.class, new ActionItemRemove.Serializer(), owner); + api.actions().register("itemClear", ActionItemClear.class, new ActionItemClear.Serializer(), owner); + api.actions().register("inventoryClear", ActionInventoryClear.class, new ActionInventoryClear.Serializer(), owner); + api.actions().register("message", ActionMessage.class, new ActionMessage.Serializer(), owner); + api.actions().register("broadcast", ActionBroadcast.class, new ActionBroadcast.Serializer(), owner); + api.actions().register("miniMessage", ActionMiniMessage.class, new ActionMiniMessage.Serializer(), owner); + api.actions().register("potionEffect", ActionPotionEffect.class, new ActionPotionEffect.Serializer(), owner); + api.actions().register("removePotionEffect", ActionPotionEffectRemove.class, new ActionPotionEffectRemove.Serializer(), owner); + api.actions().register("removeGroup", ActionGroupRemove.class, new ActionGroupRemove.Serializer(), owner); + api.actions().register("removePermission", ActionPermissionRemove.class, new ActionPermissionRemove.Serializer(), owner); + api.actions().register("setFoodLevel", ActionFoodLevelSet.class, new ActionFoodLevelSet.Serializer(), owner); + api.actions().register("setHealth", ActionHealthSet.class, new ActionHealthSet.Serializer(), owner); + api.actions().register("sound", ActionSound.class, new ActionSound.Serializer(), owner); + + try { + // SoundCategory missing on legacy Bukkit + api.actions().register("customSound", ActionSoundCustom.class, new ActionSoundCustom.Serializer(), owner); + } catch (Throwable ignore) { + } + + api.actions().register("takeLevel", ActionLevelTake.class, new ActionLevelTake.Serializer(), owner); + api.actions().register("takeMoney", ActionMoneyTake.class, new ActionMoneyTake.Serializer(), owner); + api.actions().register("takeXp", ActionXpTake.class, new ActionXpTake.Serializer(), owner); + api.actions().register("teleport", ActionTeleport.class, new ActionTeleport.Serializer(), owner); + api.actions().register("openBook", ActionBookOpen.class, new ActionBookOpen.Serializer(), owner); + api.actions().register("delay", ActionDelay.class, new ActionDelay.Serializer(), owner); + api.actions().register("setSkin", ActionSkinSet.class, new ActionSkinSet.Serializer(), owner); + api.actions().register("resetSkin", ActionSkinReset.class, new ActionSkinReset.Serializer(), owner); + api.actions().register("addRecipe", ActionRecipeAdd.class, new ActionRecipeAdd.Serializer(), owner); + api.actions().register("pageNext", ActionPageNext.class, new ActionPageNext.Serializer(), owner); + api.actions().register("pagePrev", ActionPagePrev.class, new ActionPagePrev.Serializer(), owner); + api.actions().register("bulk", ActionBulk.class, new ActionBulk.Serializer(), owner); + api.actions().register("setProperty", ActionPropertySet.class, new ActionPropertySet.Serializer(), owner); + api.actions().register("remProperty", ActionPropertyRemove.class, new ActionPropertyRemove.Serializer(), owner); + api.actions().register("refreshItem", ActionItemRefresh.class, new ActionItemRefresh.Serializer(), owner); + api.actions().register("randActions", ActionRandomActions.class, new ActionRandomActions.Serializer(), owner); + api.actions().register("playerChat", ActionPlayerChat.class, new ActionPlayerChat.Serializer(), owner); + api.actions().register("setGamemode", ActionGameModeSet.class, new ActionGameModeSet.Serializer(), owner); + api.actions().register("inputChat", ActionInputChat.class, new ActionInputChat.Serializer(), owner); + api.actions().register("setButton", ActionButtonSet.class, new ActionButtonSet.Serializer(), owner); + api.actions().register("removeButton", ActionButtonRemove.class, new ActionButtonRemove.Serializer(), owner); + api.actions().register("removePlaced", ActionPlacedItemRemove.class, new ActionPlacedItemRemove.Serializer(), owner); + api.actions().register("placeItem", ActionPlaceItem.class, new ActionPlaceItem.Serializer(), owner); + + api.actions().register("setVar", ActionVarSet.class, new ActionVarSet.Serializer(), owner); + api.actions().register("removeVar", ActionVarRem.class, new ActionVarRem.Serializer(), owner); + api.actions().register("incVar", ActionVarInc.class, new ActionVarInc.Serializer(), owner); + api.actions().register("decVar", ActionVarDec.class, new ActionVarDec.Serializer(), owner); + api.actions().register("mulVar", ActionVarMul.class, new ActionVarMul.Serializer(), owner); + api.actions().register("divVar", ActionVarDiv.class, new ActionVarDiv.Serializer(), owner); + + api.actions().register("setVarp", ActionVarpSet.class, new ActionVarpSet.Serializer(), owner); + api.actions().register("removeVarp", ActionVarpRem.class, new ActionVarpRem.Serializer(), owner); + api.actions().register("incVarp", ActionVarpInc.class, new ActionVarpInc.Serializer(), owner); + api.actions().register("decVarp", ActionVarpDec.class, new ActionVarpDec.Serializer(), owner); + api.actions().register("mulVarp", ActionVarpMul.class, new ActionVarpMul.Serializer(), owner); + api.actions().register("divVarp", ActionVarpDiv.class, new ActionVarpDiv.Serializer(), owner); + + api.actions().register("print", ActionLog.class, new ActionLog.Serializer(), owner); } } From d46391f638b9a7fbc01c568b5e70ae082e042800 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Wed, 22 Apr 2026 21:00:02 +0500 Subject: [PATCH 23/71] feat(core): populate CoreRulesBundle from MenuRules.init Co-Authored-By: Claude Opus 4.7 (1M context) --- .../abstractmenus/core/CoreRulesBundle.java | 64 ++++++++++++++++++- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/plugin/src/main/java/ru/abstractmenus/core/CoreRulesBundle.java b/plugin/src/main/java/ru/abstractmenus/core/CoreRulesBundle.java index 2a23ce3..91c3b64 100644 --- a/plugin/src/main/java/ru/abstractmenus/core/CoreRulesBundle.java +++ b/plugin/src/main/java/ru/abstractmenus/core/CoreRulesBundle.java @@ -2,10 +2,70 @@ import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.MenuExtension; +import ru.abstractmenus.data.rules.RuleChance; +import ru.abstractmenus.data.rules.RuleIf; +import ru.abstractmenus.data.rules.RuleFoodLevel; +import ru.abstractmenus.data.rules.RuleGameMode; +import ru.abstractmenus.data.rules.RuleGroup; +import ru.abstractmenus.data.rules.RuleHealth; +import ru.abstractmenus.data.rules.RuleInventoryItem; +import ru.abstractmenus.data.rules.RuleHeldItem; +import ru.abstractmenus.data.rules.RuleLevel; +import ru.abstractmenus.data.rules.RuleMoney; +import ru.abstractmenus.data.rules.RuleOnline; +import ru.abstractmenus.data.rules.RuleBungeeOnline; +import ru.abstractmenus.data.rules.RulePermission; +import ru.abstractmenus.data.rules.RuleWorld; +import ru.abstractmenus.data.rules.RuleXp; +import ru.abstractmenus.data.rules.RuleExistVar; +import ru.abstractmenus.data.rules.RuleExistVarp; +import ru.abstractmenus.data.rules.RuleFreeSlot; +import ru.abstractmenus.data.rules.RuleFreeSlotCount; +import ru.abstractmenus.data.rules.RuleRegion; +import ru.abstractmenus.data.rules.RuleJS; +import ru.abstractmenus.data.rules.RuleBungeeIsOnline; +import ru.abstractmenus.data.rules.RulePlayerIsOnline; +import ru.abstractmenus.data.rules.RulePlacedItem; +import ru.abstractmenus.data.rules.logical.RuleOneOf; +import ru.abstractmenus.data.rules.logical.RuleOr; +import ru.abstractmenus.data.rules.logical.RuleAnd; +import ru.abstractmenus.data.rules.logical.RulePlayerScope; -/** Stub — populated in the MenuRules → bundle migration task. */ +/** + * Core rule registrations. Mirrors {@code MenuRules.init()} before the + * SPI refactor. + */ final class CoreRulesBundle { + void register(AbstractMenusApi api, MenuExtension owner) { - // intentionally empty — content migrates from MenuRules.init() + api.rules().register("chance", RuleChance.class, new RuleChance.Serializer(), owner); + api.rules().register("if", RuleIf.class, new RuleIf.Serializer(), owner); + api.rules().register("foodLevel", RuleFoodLevel.class, new RuleFoodLevel.Serializer(), owner); + api.rules().register("gamemode", RuleGameMode.class, new RuleGameMode.Serializer(), owner); + api.rules().register("group", RuleGroup.class, new RuleGroup.Serializer(), owner); + api.rules().register("health", RuleHealth.class, new RuleHealth.Serializer(), owner); + api.rules().register("inventoryItems", RuleInventoryItem.class, new RuleInventoryItem.Serializer(), owner); + api.rules().register("heldItem", RuleHeldItem.class, new RuleHeldItem.Serializer(), owner); + api.rules().register("level", RuleLevel.class, new RuleLevel.Serializer(), owner); + api.rules().register("money", RuleMoney.class, new RuleMoney.Serializer(), owner); + api.rules().register("online", RuleOnline.class, new RuleOnline.Serializer(), owner); + api.rules().register("bungeeOnline", RuleBungeeOnline.class, new RuleBungeeOnline.Serializer(), owner); + api.rules().register("permission", RulePermission.class, new RulePermission.Serializer(), owner); + api.rules().register("world", RuleWorld.class, new RuleWorld.Serializer(), owner); + api.rules().register("xp", RuleXp.class, new RuleXp.Serializer(), owner); + api.rules().register("existVar", RuleExistVar.class, new RuleExistVar.Serializer(), owner); + api.rules().register("existVarp", RuleExistVarp.class, new RuleExistVarp.Serializer(), owner); + api.rules().register("freeSlot", RuleFreeSlot.class, new RuleFreeSlot.Serializer(), owner); + api.rules().register("freeSlotCount", RuleFreeSlotCount.class, new RuleFreeSlotCount.Serializer(), owner); + api.rules().register("region", RuleRegion.class, new RuleRegion.Serializer(), owner); + api.rules().register("js", RuleJS.class, new RuleJS.Serializer(), owner); + api.rules().register("bungeeIsOnline", RuleBungeeIsOnline.class, new RuleBungeeIsOnline.Serializer(), owner); + api.rules().register("playerIsOnline", RulePlayerIsOnline.class, new RulePlayerIsOnline.Serializer(), owner); + api.rules().register("placedItem", RulePlacedItem.class, new RulePlacedItem.Serializer(), owner); + + api.rules().register("and", RuleAnd.class, new RuleAnd.Serializer(), owner); + api.rules().register("or", RuleOr.class, new RuleOr.Serializer(), owner); + api.rules().register("oneof", RuleOneOf.class, new RuleOneOf.Serializer(), owner); + api.rules().register("playerScope", RulePlayerScope.class, new RulePlayerScope.Serializer(), owner); } } From 52fb1eca073daaa021328233c4d054c9ae526c71 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Wed, 22 Apr 2026 21:01:44 +0500 Subject: [PATCH 24/71] feat(core): populate CoreItemPropsBundle from ItemProps.init Co-Authored-By: Claude Opus 4.7 (1M context) --- .../core/CoreItemPropsBundle.java | 73 ++++++++++++++++++- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/plugin/src/main/java/ru/abstractmenus/core/CoreItemPropsBundle.java b/plugin/src/main/java/ru/abstractmenus/core/CoreItemPropsBundle.java index c71b48f..dc90e72 100644 --- a/plugin/src/main/java/ru/abstractmenus/core/CoreItemPropsBundle.java +++ b/plugin/src/main/java/ru/abstractmenus/core/CoreItemPropsBundle.java @@ -2,10 +2,79 @@ import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.MenuExtension; +import ru.abstractmenus.data.properties.PropMaterial; +import ru.abstractmenus.data.properties.PropTexture; +import ru.abstractmenus.data.properties.PropSkullOwner; +import ru.abstractmenus.data.properties.PropHDB; +import ru.abstractmenus.data.properties.PropMmoItem; +import ru.abstractmenus.data.properties.PropItemsAdder; +import ru.abstractmenus.data.properties.PropOraxen; +import ru.abstractmenus.data.properties.PropEquipItem; +import ru.abstractmenus.data.properties.PropSerialized; +import ru.abstractmenus.data.properties.PropName; +import ru.abstractmenus.data.properties.PropData; +import ru.abstractmenus.data.properties.PropCount; +import ru.abstractmenus.data.properties.PropLore; +import ru.abstractmenus.data.properties.PropGlow; +import ru.abstractmenus.data.properties.PropEnchantments; +import ru.abstractmenus.data.properties.PropColor; +import ru.abstractmenus.data.properties.PropFlags; +import ru.abstractmenus.data.properties.PropUnbreakable; +import ru.abstractmenus.data.properties.PropPotionData; +import ru.abstractmenus.data.properties.PropFireworkData; +import ru.abstractmenus.data.properties.PropAttributeModifier; +import ru.abstractmenus.data.properties.PropBookData; +import ru.abstractmenus.data.properties.PropBannerData; +import ru.abstractmenus.data.properties.PropShieldData; +import ru.abstractmenus.data.properties.PropModel; +import ru.abstractmenus.data.properties.PropEnchantStore; +import ru.abstractmenus.data.properties.PropKnowledgeBook; +import ru.abstractmenus.data.properties.PropDamage; +import ru.abstractmenus.data.properties.PropNbt; +import ru.abstractmenus.data.properties.PropBindings; +import ru.abstractmenus.data.properties.PropNameLight; +import ru.abstractmenus.data.properties.PropLoreLight; -/** Stub — populated in the ItemProps → bundle migration task. */ +/** + * Core item property registrations. Mirrors {@code ItemProps.init()} before + * the SPI refactor. + */ final class CoreItemPropsBundle { + void register(AbstractMenusApi api, MenuExtension owner) { - // intentionally empty — content migrates from ItemProps.init() + api.itemProperties().register("material", PropMaterial.class, new PropMaterial.Serializer(), owner); + api.itemProperties().register("texture", PropTexture.class, new PropTexture.Serializer(), owner); + api.itemProperties().register("skullOwner", PropSkullOwner.class, new PropSkullOwner.Serializer(), owner); + api.itemProperties().register("hdb", PropHDB.class, new PropHDB.Serializer(), owner); + api.itemProperties().register("mmoitem", PropMmoItem.class, new PropMmoItem.Serializer(), owner); + api.itemProperties().register("itemsAdder", PropItemsAdder.class, new PropItemsAdder.Serializer(), owner); + api.itemProperties().register("oraxen", PropOraxen.class, new PropOraxen.Serializer(), owner); + api.itemProperties().register("equipItem", PropEquipItem.class, new PropEquipItem.Serializer(), owner); + api.itemProperties().register("serialized", PropSerialized.class, new PropSerialized.Serializer(), owner); + + api.itemProperties().register("name", PropName.class, new PropName.Serializer(), owner); + api.itemProperties().register("data", PropData.class, new PropData.Serializer(), owner); + api.itemProperties().register("count", PropCount.class, new PropCount.Serializer(), owner); + api.itemProperties().register("lore", PropLore.class, new PropLore.Serializer(), owner); + api.itemProperties().register("glow", PropGlow.class, new PropGlow.Serializer(), owner); + api.itemProperties().register("enchantments", PropEnchantments.class, new PropEnchantments.Serializer(), owner); + api.itemProperties().register("color", PropColor.class, new PropColor.Serializer(), owner); + api.itemProperties().register("flags", PropFlags.class, new PropFlags.Serializer(), owner); + api.itemProperties().register("unbreakable", PropUnbreakable.class, new PropUnbreakable.Serializer(), owner); + api.itemProperties().register("potionData", PropPotionData.class, new PropPotionData.Serializer(), owner); + api.itemProperties().register("fireworkData", PropFireworkData.class, new PropFireworkData.Serializer(), owner); + api.itemProperties().register("attributeModifier", PropAttributeModifier.class, new PropAttributeModifier.Serializer(), owner); + api.itemProperties().register("bookData", PropBookData.class, new PropBookData.Serializer(), owner); + api.itemProperties().register("bannerData", PropBannerData.class, new PropBannerData.Serializer(), owner); + api.itemProperties().register("shieldData", PropShieldData.class, new PropShieldData.Serializer(), owner); + api.itemProperties().register("model", PropModel.class, new PropModel.Serializer(), owner); + api.itemProperties().register("enchantStore", PropEnchantStore.class, new PropEnchantStore.Serializer(), owner); + api.itemProperties().register("recipes", PropKnowledgeBook.class, new PropKnowledgeBook.Serializer(), owner); + api.itemProperties().register("damage", PropDamage.class, new PropDamage.Serializer(), owner); + api.itemProperties().register("nbt", PropNbt.class, new PropNbt.Serializer(), owner); + api.itemProperties().register("bindings", PropBindings.class, new PropBindings.Serializer(), owner); + + api.itemProperties().register("nameLight", PropNameLight.class, new PropNameLight.Serializer(), owner); + api.itemProperties().register("loreLight", PropLoreLight.class, new PropLoreLight.Serializer(), owner); } } From 292b4328118c536283f4c28cda3cf81f4345dd4b Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Wed, 22 Apr 2026 21:03:06 +0500 Subject: [PATCH 25/71] feat(core): populate CoreActivatorsBundle from Activators.init Co-Authored-By: Claude Opus 4.7 (1M context) --- .../core/CoreActivatorsBundle.java | 48 ++++++++++++++++++- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/plugin/src/main/java/ru/abstractmenus/core/CoreActivatorsBundle.java b/plugin/src/main/java/ru/abstractmenus/core/CoreActivatorsBundle.java index aa70796..728eafc 100644 --- a/plugin/src/main/java/ru/abstractmenus/core/CoreActivatorsBundle.java +++ b/plugin/src/main/java/ru/abstractmenus/core/CoreActivatorsBundle.java @@ -1,11 +1,55 @@ package ru.abstractmenus.core; +import ru.abstractmenus.AbstractMenus; import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.MenuExtension; +import ru.abstractmenus.data.activators.OpenButton; +import ru.abstractmenus.data.activators.OpenChat; +import ru.abstractmenus.data.activators.OpenChatContains; +import ru.abstractmenus.data.activators.OpenClickBlock; +import ru.abstractmenus.data.activators.OpenClickBlockType; +import ru.abstractmenus.data.activators.OpenClickEntity; +import ru.abstractmenus.data.activators.OpenClickItem; +import ru.abstractmenus.data.activators.OpenClickNPC; +import ru.abstractmenus.data.activators.OpenCommand; +import ru.abstractmenus.data.activators.OpenJoin; +import ru.abstractmenus.data.activators.OpenLever; +import ru.abstractmenus.data.activators.OpenPlate; +import ru.abstractmenus.data.activators.OpenRegionEnter; +import ru.abstractmenus.data.activators.OpenRegionLeave; +import ru.abstractmenus.data.activators.OpenShiftClickEntity; +import ru.abstractmenus.data.activators.OpenSign; +import ru.abstractmenus.data.activators.OpenSwapItems; -/** Stub — populated in the Activators → bundle migration task. */ +/** + * Core activator registrations. Mirrors {@code Activators.init()} before + * the SPI refactor. + */ final class CoreActivatorsBundle { + void register(AbstractMenusApi api, MenuExtension owner) { - // intentionally empty — content migrates from Activators.init() + api.activators().register("command", OpenCommand.class, new OpenCommand.Serializer(), owner); + api.activators().register("chat", OpenChat.class, new OpenChat.Serializer(), owner); + api.activators().register("containsChat", OpenChatContains.class, new OpenChatContains.Serializer(), owner); + api.activators().register("join", OpenJoin.class, new OpenJoin.Serializer(), owner); + api.activators().register("clickEntity", OpenClickEntity.class, new OpenClickEntity.Serializer(), owner); + api.activators().register("shiftClickEntity", OpenShiftClickEntity.class, new OpenShiftClickEntity.Serializer(), owner); + api.activators().register("clickItem", OpenClickItem.class, new OpenClickItem.Serializer(), owner); + api.activators().register("button", OpenButton.class, new OpenButton.Serializer(), owner); + api.activators().register("lever", OpenLever.class, new OpenLever.Serializer(), owner); + api.activators().register("plate", OpenPlate.class, new OpenPlate.Serializer(), owner); + api.activators().register("table", OpenSign.class, new OpenSign.Serializer(), owner); + api.activators().register("clickBlock", OpenClickBlock.class, new OpenClickBlock.Serializer(), owner); + api.activators().register("clickBlockType", OpenClickBlockType.class, new OpenClickBlockType.Serializer(), owner); + api.activators().register("swapItems", OpenSwapItems.class, new OpenSwapItems.Serializer(), owner); + + if (AbstractMenus.checkDependency("Citizens")) { + api.activators().register("clickNPC", OpenClickNPC.class, new OpenClickNPC.Serializer(), owner); + } + + if (AbstractMenus.checkDependency("WorldGuard")) { + api.activators().register("regionJoin", OpenRegionEnter.class, new OpenRegionEnter.Serializer(), owner); + api.activators().register("regionLeave", OpenRegionLeave.class, new OpenRegionLeave.Serializer(), owner); + } } } From 45a679497099be0496572d59bf5c5e3352e113b7 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Wed, 22 Apr 2026 21:04:25 +0500 Subject: [PATCH 26/71] feat(core): populate CoreCatalogsBundle from Catalogs.init Co-Authored-By: Claude Opus 4.7 (1M context) --- .../core/CoreCatalogsBundle.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/plugin/src/main/java/ru/abstractmenus/core/CoreCatalogsBundle.java b/plugin/src/main/java/ru/abstractmenus/core/CoreCatalogsBundle.java index 6cb4aff..fb66d36 100644 --- a/plugin/src/main/java/ru/abstractmenus/core/CoreCatalogsBundle.java +++ b/plugin/src/main/java/ru/abstractmenus/core/CoreCatalogsBundle.java @@ -2,10 +2,25 @@ import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.MenuExtension; +import ru.abstractmenus.data.catalogs.IteratorCatalog; +import ru.abstractmenus.data.catalogs.PlayerCatalog; +import ru.abstractmenus.data.catalogs.EntityCatalog; +import ru.abstractmenus.data.catalogs.WorldCatalog; +import ru.abstractmenus.data.catalogs.ServerCatalog; +import ru.abstractmenus.data.catalogs.SliceCatalog; -/** Stub — populated in the Catalogs → bundle migration task. */ +/** + * Core catalog registrations. Mirrors {@code Catalogs.init()} before the + * SPI refactor. + */ final class CoreCatalogsBundle { + void register(AbstractMenusApi api, MenuExtension owner) { - // intentionally empty — content migrates from Catalogs.init() + api.catalogs().register("iterator", IteratorCatalog.class, new IteratorCatalog.Serializer(), owner); + api.catalogs().register("players", PlayerCatalog.class, new PlayerCatalog.Serializer(), owner); + api.catalogs().register("entities", EntityCatalog.class, new EntityCatalog.Serializer(), owner); + api.catalogs().register("worlds", WorldCatalog.class, new WorldCatalog.Serializer(), owner); + api.catalogs().register("bungee_servers", ServerCatalog.class, new ServerCatalog.Serializer(), owner); + api.catalogs().register("slice", SliceCatalog.class, new SliceCatalog.Serializer(), owner); } } From fd8f0ba9caf907b7e44c6843a22f546380c7260c Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Wed, 22 Apr 2026 21:23:59 +0500 Subject: [PATCH 27/71] refactor(plugin): switch core registration to CoreExtension (dogfood SPI) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removes MenuActions/MenuRules/ItemProps/Activators/Catalogs — content lives in their Core*Bundle counterparts, invoked through CoreExtension via the new AbstractMenusApi SPI. ItemProps.BINDINGS (a public constant co-located with the registrations) was inlined as "bindings" literal in CoreItemPropsBundle during the earlier migration; this commit also updates ActionPropertySet to use the literal directly, removing the last remaining external dependency on ItemProps. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../java/ru/abstractmenus/AbstractMenus.java | 18 ++-- .../data/actions/ActionPropertySet.java | 3 +- .../data/actions/MenuActions.java | 93 ------------------- .../data/activators/Activators.java | 37 -------- .../abstractmenus/data/catalogs/Catalogs.java | 18 ---- .../data/properties/ItemProps.java | 47 ---------- .../abstractmenus/data/rules/MenuRules.java | 46 --------- 7 files changed, 9 insertions(+), 253 deletions(-) delete mode 100644 plugin/src/main/java/ru/abstractmenus/data/actions/MenuActions.java delete mode 100644 plugin/src/main/java/ru/abstractmenus/data/activators/Activators.java delete mode 100644 plugin/src/main/java/ru/abstractmenus/data/catalogs/Catalogs.java delete mode 100644 plugin/src/main/java/ru/abstractmenus/data/properties/ItemProps.java delete mode 100644 plugin/src/main/java/ru/abstractmenus/data/rules/MenuRules.java diff --git a/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java b/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java index 6c92ed8..6924703 100644 --- a/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java +++ b/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java @@ -29,11 +29,8 @@ import ru.abstractmenus.commands.am.CommandServe; import ru.abstractmenus.commands.var.*; import ru.abstractmenus.commands.varp.*; -import ru.abstractmenus.data.actions.MenuActions; -import ru.abstractmenus.data.activators.Activators; -import ru.abstractmenus.data.catalogs.Catalogs; -import ru.abstractmenus.data.properties.ItemProps; -import ru.abstractmenus.data.rules.MenuRules; +import ru.abstractmenus.api.MenuExtension; +import ru.abstractmenus.core.CoreExtension; import ru.abstractmenus.handlers.*; import ru.abstractmenus.handlers.placeholder.PlaceholderCustomHandler; import ru.abstractmenus.handlers.placeholder.PlaceholderDefaultHandler; @@ -151,11 +148,12 @@ public void onEnable() { registerCommands(config); Serializers.init(this); - ItemProps.init(); - Activators.init(); - MenuActions.init(); - MenuRules.init(); - Catalogs.init(); + + // Core extension — dogfood: the plugin registers its own types through + // the same SPI external addons will use. + MenuExtension core = new CoreExtension(); + core.onLoad(api); + core.onEnable(api); loadMenus(); diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPropertySet.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPropertySet.java index c7f3501..e8f7e55 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPropertySet.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPropertySet.java @@ -1,6 +1,5 @@ package ru.abstractmenus.data.actions; -import ru.abstractmenus.data.properties.ItemProps; import ru.abstractmenus.datatype.TypeSlot; import ru.abstractmenus.hocon.api.ConfigNode; import ru.abstractmenus.hocon.api.serialize.NodeSerializeException; @@ -40,7 +39,7 @@ public void activate(Player player, Menu menu, Item clickedItem) { slot.getSlots(index -> { // Just modifying ItemStack from inventory won't work because remProperty breaks it Item item = menu.getItem(index); - item.removeProperty(ItemProps.BINDINGS); + item.removeProperty("bindings"); item.setProperties(properties); menu.setItem(SlotIndex.of(index), item, player); }); diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/MenuActions.java b/plugin/src/main/java/ru/abstractmenus/data/actions/MenuActions.java deleted file mode 100644 index f7fb1ff..0000000 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/MenuActions.java +++ /dev/null @@ -1,93 +0,0 @@ -package ru.abstractmenus.data.actions; - -import ru.abstractmenus.api.Types; -import ru.abstractmenus.data.actions.var.*; -import ru.abstractmenus.data.actions.varp.*; -import ru.abstractmenus.data.actions.wrappers.ActionBulk; -import ru.abstractmenus.data.actions.wrappers.ActionDelay; -import ru.abstractmenus.data.actions.wrappers.ActionPlayerScope; -import ru.abstractmenus.data.actions.wrappers.ActionRandomActions; - -public final class MenuActions { - - private MenuActions() { - } - - public static void init() { - Types.registerAction("closeMenu", ActionMenuClose.class, new ActionMenuClose.Serializer()); - Types.registerAction("openMenu", ActionMenuOpen.class, new ActionMenuOpen.Serializer()); - Types.registerAction("openMenuCtx", ActionMenuOpenCtx.class, new ActionMenuOpenCtx.Serializer()); - Types.registerAction("playerScope", ActionPlayerScope.class, new ActionPlayerScope.Serializer()); - - Types.registerAction("addGroup", ActionGroupAdd.class, new ActionGroupAdd.Serializer()); - Types.registerAction("bungeeConnect", ActionBungeeConnect.class, new ActionBungeeConnect.Serializer()); - Types.registerAction("refreshMenu", ActionMenuRefresh.class, new ActionMenuRefresh.Serializer()); - Types.registerAction("command", ActionCommand.class, new ActionCommand.Serializer()); - Types.registerAction("giveLevel", ActionLevelGive.class, new ActionLevelGive.Serializer()); - Types.registerAction("giveMoney", ActionMoneyGive.class, new ActionMoneyGive.Serializer()); - Types.registerAction("givePermission", ActionPermissionGive.class, new ActionPermissionGive.Serializer()); - Types.registerAction("lpMetaSet", ActionLuckPermsMetaSet.class, new ActionLuckPermsMetaSet.Serializer()); - Types.registerAction("lpMetaRemove", ActionLuckPermsMetaRemove.class, new ActionLuckPermsMetaRemove.Serializer()); - Types.registerAction("giveXp", ActionXpGive.class, new ActionXpGive.Serializer()); - Types.registerAction("itemAdd", ActionItemAdd.class, new ActionItemAdd.Serializer()); - Types.registerAction("itemRemove", ActionItemRemove.class, new ActionItemRemove.Serializer()); - Types.registerAction("itemClear", ActionItemClear.class, new ActionItemClear.Serializer()); - Types.registerAction("inventoryClear", ActionInventoryClear.class, new ActionInventoryClear.Serializer()); - Types.registerAction("message", ActionMessage.class, new ActionMessage.Serializer()); - Types.registerAction("broadcast", ActionBroadcast.class, new ActionBroadcast.Serializer()); - Types.registerAction("miniMessage", ActionMiniMessage.class, new ActionMiniMessage.Serializer()); - Types.registerAction("potionEffect", ActionPotionEffect.class, new ActionPotionEffect.Serializer()); - Types.registerAction("removePotionEffect", ActionPotionEffectRemove.class, new ActionPotionEffectRemove.Serializer()); - Types.registerAction("removeGroup", ActionGroupRemove.class, new ActionGroupRemove.Serializer()); - Types.registerAction("removePermission", ActionPermissionRemove.class, new ActionPermissionRemove.Serializer()); - Types.registerAction("setFoodLevel", ActionFoodLevelSet.class, new ActionFoodLevelSet.Serializer()); - Types.registerAction("setHealth", ActionHealthSet.class, new ActionHealthSet.Serializer()); - Types.registerAction("sound", ActionSound.class, new ActionSound.Serializer()); - - try { - // SoundCategory missing on legacy Bukkit - Types.registerAction("customSound", ActionSoundCustom.class, new ActionSoundCustom.Serializer()); - } catch (Throwable ignore) { - } - - Types.registerAction("takeLevel", ActionLevelTake.class, new ActionLevelTake.Serializer()); - Types.registerAction("takeMoney", ActionMoneyTake.class, new ActionMoneyTake.Serializer()); - Types.registerAction("takeXp", ActionXpTake.class, new ActionXpTake.Serializer()); - Types.registerAction("teleport", ActionTeleport.class, new ActionTeleport.Serializer()); - Types.registerAction("openBook", ActionBookOpen.class, new ActionBookOpen.Serializer()); - Types.registerAction("delay", ActionDelay.class, new ActionDelay.Serializer()); - Types.registerAction("setSkin", ActionSkinSet.class, new ActionSkinSet.Serializer()); - Types.registerAction("resetSkin", ActionSkinReset.class, new ActionSkinReset.Serializer()); - Types.registerAction("addRecipe", ActionRecipeAdd.class, new ActionRecipeAdd.Serializer()); - Types.registerAction("pageNext", ActionPageNext.class, new ActionPageNext.Serializer()); - Types.registerAction("pagePrev", ActionPagePrev.class, new ActionPagePrev.Serializer()); - Types.registerAction("bulk", ActionBulk.class, new ActionBulk.Serializer()); - Types.registerAction("setProperty", ActionPropertySet.class, new ActionPropertySet.Serializer()); - Types.registerAction("remProperty", ActionPropertyRemove.class, new ActionPropertyRemove.Serializer()); - Types.registerAction("refreshItem", ActionItemRefresh.class, new ActionItemRefresh.Serializer()); - Types.registerAction("randActions", ActionRandomActions.class, new ActionRandomActions.Serializer()); - Types.registerAction("playerChat", ActionPlayerChat.class, new ActionPlayerChat.Serializer()); - Types.registerAction("setGamemode", ActionGameModeSet.class, new ActionGameModeSet.Serializer()); - Types.registerAction("inputChat", ActionInputChat.class, new ActionInputChat.Serializer()); - Types.registerAction("setButton", ActionButtonSet.class, new ActionButtonSet.Serializer()); - Types.registerAction("removeButton", ActionButtonRemove.class, new ActionButtonRemove.Serializer()); - Types.registerAction("removePlaced", ActionPlacedItemRemove.class, new ActionPlacedItemRemove.Serializer()); - Types.registerAction("placeItem", ActionPlaceItem.class, new ActionPlaceItem.Serializer()); - - Types.registerAction("setVar", ActionVarSet.class, new ActionVarSet.Serializer()); - Types.registerAction("removeVar", ActionVarRem.class, new ActionVarRem.Serializer()); - Types.registerAction("incVar", ActionVarInc.class, new ActionVarInc.Serializer()); - Types.registerAction("decVar", ActionVarDec.class, new ActionVarDec.Serializer()); - Types.registerAction("mulVar", ActionVarMul.class, new ActionVarMul.Serializer()); - Types.registerAction("divVar", ActionVarDiv.class, new ActionVarDiv.Serializer()); - - Types.registerAction("setVarp", ActionVarpSet.class, new ActionVarpSet.Serializer()); - Types.registerAction("removeVarp", ActionVarpRem.class, new ActionVarpRem.Serializer()); - Types.registerAction("incVarp", ActionVarpInc.class, new ActionVarpInc.Serializer()); - Types.registerAction("decVarp", ActionVarpDec.class, new ActionVarpDec.Serializer()); - Types.registerAction("mulVarp", ActionVarpMul.class, new ActionVarpMul.Serializer()); - Types.registerAction("divVarp", ActionVarpDiv.class, new ActionVarpDiv.Serializer()); - - Types.registerAction("print", ActionLog.class, new ActionLog.Serializer()); - } -} diff --git a/plugin/src/main/java/ru/abstractmenus/data/activators/Activators.java b/plugin/src/main/java/ru/abstractmenus/data/activators/Activators.java deleted file mode 100644 index 95d973e..0000000 --- a/plugin/src/main/java/ru/abstractmenus/data/activators/Activators.java +++ /dev/null @@ -1,37 +0,0 @@ -package ru.abstractmenus.data.activators; - -import ru.abstractmenus.AbstractMenus; -import ru.abstractmenus.api.Types; - -public final class Activators { - - private Activators() { - } - - public static void init() { - Types.registerActivator("command", OpenCommand.class, new OpenCommand.Serializer()); - Types.registerActivator("chat", OpenChat.class, new OpenChat.Serializer()); - Types.registerActivator("containsChat", OpenChatContains.class, new OpenChatContains.Serializer()); - Types.registerActivator("join", OpenJoin.class, new OpenJoin.Serializer()); - Types.registerActivator("clickEntity", OpenClickEntity.class, new OpenClickEntity.Serializer()); - Types.registerActivator("shiftClickEntity", OpenShiftClickEntity.class, new OpenShiftClickEntity.Serializer()); - Types.registerActivator("clickItem", OpenClickItem.class, new OpenClickItem.Serializer()); - Types.registerActivator("button", OpenButton.class, new OpenButton.Serializer()); - Types.registerActivator("lever", OpenLever.class, new OpenLever.Serializer()); - Types.registerActivator("plate", OpenPlate.class, new OpenPlate.Serializer()); - Types.registerActivator("table", OpenSign.class, new OpenSign.Serializer()); - Types.registerActivator("clickBlock", OpenClickBlock.class, new OpenClickBlock.Serializer()); - Types.registerActivator("clickBlockType", OpenClickBlockType.class, new OpenClickBlockType.Serializer()); - Types.registerActivator("swapItems", OpenSwapItems.class, new OpenSwapItems.Serializer()); - - if (AbstractMenus.checkDependency("Citizens")) { - Types.registerActivator("clickNPC", OpenClickNPC.class, new OpenClickNPC.Serializer()); - } - - if (AbstractMenus.checkDependency("WorldGuard")) { - Types.registerActivator("regionJoin", OpenRegionEnter.class, new OpenRegionEnter.Serializer()); - Types.registerActivator("regionLeave", OpenRegionLeave.class, new OpenRegionLeave.Serializer()); - } - } - -} diff --git a/plugin/src/main/java/ru/abstractmenus/data/catalogs/Catalogs.java b/plugin/src/main/java/ru/abstractmenus/data/catalogs/Catalogs.java deleted file mode 100644 index e73a3d9..0000000 --- a/plugin/src/main/java/ru/abstractmenus/data/catalogs/Catalogs.java +++ /dev/null @@ -1,18 +0,0 @@ -package ru.abstractmenus.data.catalogs; - -import ru.abstractmenus.api.Types; - -public final class Catalogs { - - private Catalogs(){} - - public static void init() { - Types.registerCatalog("iterator", IteratorCatalog.class, new IteratorCatalog.Serializer()); - Types.registerCatalog("players", PlayerCatalog.class, new PlayerCatalog.Serializer()); - Types.registerCatalog("entities", EntityCatalog.class, new EntityCatalog.Serializer()); - Types.registerCatalog("worlds", WorldCatalog.class, new WorldCatalog.Serializer()); - Types.registerCatalog("bungee_servers", ServerCatalog.class, new ServerCatalog.Serializer()); - Types.registerCatalog("slice", SliceCatalog.class, new SliceCatalog.Serializer()); - } - -} diff --git a/plugin/src/main/java/ru/abstractmenus/data/properties/ItemProps.java b/plugin/src/main/java/ru/abstractmenus/data/properties/ItemProps.java deleted file mode 100644 index 4bf9720..0000000 --- a/plugin/src/main/java/ru/abstractmenus/data/properties/ItemProps.java +++ /dev/null @@ -1,47 +0,0 @@ -package ru.abstractmenus.data.properties; - -import ru.abstractmenus.api.Types; - -public final class ItemProps { - - public static final String BINDINGS = "bindings"; - - public static void init() { - Types.registerItemProperty("material", PropMaterial.class, new PropMaterial.Serializer()); - Types.registerItemProperty("texture", PropTexture.class, new PropTexture.Serializer()); - Types.registerItemProperty("skullOwner", PropSkullOwner.class, new PropSkullOwner.Serializer()); - Types.registerItemProperty("hdb", PropHDB.class, new PropHDB.Serializer()); - Types.registerItemProperty("mmoitem", PropMmoItem.class, new PropMmoItem.Serializer()); - Types.registerItemProperty("itemsAdder", PropItemsAdder.class, new PropItemsAdder.Serializer()); - Types.registerItemProperty("oraxen", PropOraxen.class, new PropOraxen.Serializer()); - Types.registerItemProperty("equipItem", PropEquipItem.class, new PropEquipItem.Serializer()); - Types.registerItemProperty("serialized", PropSerialized.class, new PropSerialized.Serializer()); - - Types.registerItemProperty("name", PropName.class, new PropName.Serializer()); - Types.registerItemProperty("data", PropData.class, new PropData.Serializer()); - Types.registerItemProperty("count", PropCount.class, new PropCount.Serializer()); - Types.registerItemProperty("lore", PropLore.class, new PropLore.Serializer()); - Types.registerItemProperty("glow", PropGlow.class, new PropGlow.Serializer()); - Types.registerItemProperty("enchantments", PropEnchantments.class, new PropEnchantments.Serializer()); - Types.registerItemProperty("color", PropColor.class, new PropColor.Serializer()); - Types.registerItemProperty("flags", PropFlags.class, new PropFlags.Serializer()); - Types.registerItemProperty("unbreakable", PropUnbreakable.class, new PropUnbreakable.Serializer()); - Types.registerItemProperty("potionData", PropPotionData.class, new PropPotionData.Serializer()); - Types.registerItemProperty("fireworkData", PropFireworkData.class, new PropFireworkData.Serializer()); - Types.registerItemProperty("attributeModifier", PropAttributeModifier.class, new PropAttributeModifier.Serializer()); - Types.registerItemProperty("bookData", PropBookData.class, new PropBookData.Serializer()); - Types.registerItemProperty("bannerData", PropBannerData.class, new PropBannerData.Serializer()); - Types.registerItemProperty("shieldData", PropShieldData.class, new PropShieldData.Serializer()); - Types.registerItemProperty("model", PropModel.class, new PropModel.Serializer()); - Types.registerItemProperty("enchantStore", PropEnchantStore.class, new PropEnchantStore.Serializer()); - Types.registerItemProperty("recipes", PropKnowledgeBook.class, new PropKnowledgeBook.Serializer()); - Types.registerItemProperty("damage", PropDamage.class, new PropDamage.Serializer()); - Types.registerItemProperty("nbt", PropNbt.class, new PropNbt.Serializer()); - Types.registerItemProperty(BINDINGS, PropBindings.class, new PropBindings.Serializer()); - - Types.registerItemProperty("nameLight", PropNameLight.class, new PropNameLight.Serializer()); - Types.registerItemProperty("loreLight", PropLoreLight.class, new PropLoreLight.Serializer()); - } - - private ItemProps(){} -} diff --git a/plugin/src/main/java/ru/abstractmenus/data/rules/MenuRules.java b/plugin/src/main/java/ru/abstractmenus/data/rules/MenuRules.java deleted file mode 100644 index 389f8dc..0000000 --- a/plugin/src/main/java/ru/abstractmenus/data/rules/MenuRules.java +++ /dev/null @@ -1,46 +0,0 @@ -package ru.abstractmenus.data.rules; - - -import ru.abstractmenus.api.Types; -import ru.abstractmenus.data.rules.logical.RuleOneOf; -import ru.abstractmenus.data.rules.logical.RuleOr; -import ru.abstractmenus.data.rules.logical.RuleAnd; -import ru.abstractmenus.data.rules.logical.RulePlayerScope; - -public final class MenuRules { - - private MenuRules(){} - - public static void init(){ - Types.registerRule("chance", RuleChance.class, new RuleChance.Serializer()); - Types.registerRule("if", RuleIf.class, new RuleIf.Serializer()); - Types.registerRule("foodLevel", RuleFoodLevel.class, new RuleFoodLevel.Serializer()); - Types.registerRule("gamemode", RuleGameMode.class, new RuleGameMode.Serializer()); - Types.registerRule("group", RuleGroup.class, new RuleGroup.Serializer()); - Types.registerRule("health", RuleHealth.class, new RuleHealth.Serializer()); - Types.registerRule("inventoryItems", RuleInventoryItem.class, new RuleInventoryItem.Serializer()); - Types.registerRule("heldItem", RuleHeldItem.class, new RuleHeldItem.Serializer()); - Types.registerRule("level", RuleLevel.class, new RuleLevel.Serializer()); - Types.registerRule("money", RuleMoney.class, new RuleMoney.Serializer()); - Types.registerRule("online", RuleOnline.class, new RuleOnline.Serializer()); - Types.registerRule("bungeeOnline", RuleBungeeOnline.class, new RuleBungeeOnline.Serializer()); - Types.registerRule("permission", RulePermission.class, new RulePermission.Serializer()); - Types.registerRule("world", RuleWorld.class, new RuleWorld.Serializer()); - Types.registerRule("xp", RuleXp.class, new RuleXp.Serializer()); - Types.registerRule("existVar", RuleExistVar.class, new RuleExistVar.Serializer()); - Types.registerRule("existVarp", RuleExistVarp.class, new RuleExistVarp.Serializer()); - Types.registerRule("freeSlot", RuleFreeSlot.class, new RuleFreeSlot.Serializer()); - Types.registerRule("freeSlotCount", RuleFreeSlotCount.class, new RuleFreeSlotCount.Serializer()); - Types.registerRule("region", RuleRegion.class, new RuleRegion.Serializer()); - Types.registerRule("js", RuleJS.class, new RuleJS.Serializer()); - Types.registerRule("bungeeIsOnline", RuleBungeeIsOnline.class, new RuleBungeeIsOnline.Serializer()); - Types.registerRule("playerIsOnline", RulePlayerIsOnline.class, new RulePlayerIsOnline.Serializer()); - Types.registerRule("placedItem", RulePlacedItem.class, new RulePlacedItem.Serializer()); - - Types.registerRule("and", RuleAnd.class, new RuleAnd.Serializer()); - Types.registerRule("or", RuleOr.class, new RuleOr.Serializer()); - Types.registerRule("oneof", RuleOneOf.class, new RuleOneOf.Serializer()); - Types.registerRule("playerScope", RulePlayerScope.class, new RulePlayerScope.Serializer()); - } - -} From 02d551b5badb888316b67da8fb5ed378b8ae9076 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Wed, 22 Apr 2026 21:34:59 +0500 Subject: [PATCH 28/71] refactor(plugin): migrate Types.* call sites to AbstractMenusApi.get().* Covers ~10 files under serializers/, data/, services/, placeholders/. Old Types class in :api module is now unused by plugin code; deletion in next task. Co-Authored-By: Claude Opus 4.7 (1M context) --- plugin/src/main/java/ru/abstractmenus/data/Actions.java | 4 ++-- .../ru/abstractmenus/data/actions/ActionPropertySet.java | 4 ++-- .../ru/abstractmenus/data/properties/PropBindings.java | 4 ++-- .../ru/abstractmenus/data/rules/logical/RulesGroup.java | 4 ++-- .../placeholders/hooks/ActivatorPlaceholders.java | 4 ++-- .../java/ru/abstractmenus/serializers/ItemSerializer.java | 4 ++-- .../java/ru/abstractmenus/serializers/Serializers.java | 4 ++-- .../serializers/menu/BaseMenuSerializer.java | 4 ++-- .../serializers/menu/GeneratedMenuSerializer.java | 4 ++-- .../main/java/ru/abstractmenus/services/MenuManager.java | 8 ++++---- 10 files changed, 22 insertions(+), 22 deletions(-) diff --git a/plugin/src/main/java/ru/abstractmenus/data/Actions.java b/plugin/src/main/java/ru/abstractmenus/data/Actions.java index 6491ea4..388a5b3 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/Actions.java +++ b/plugin/src/main/java/ru/abstractmenus/data/Actions.java @@ -6,12 +6,12 @@ import ru.abstractmenus.hocon.api.serialize.NodeSerializeException; import ru.abstractmenus.hocon.api.serialize.NodeSerializer; import org.bukkit.entity.Player; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.Action; import ru.abstractmenus.api.Logger; import ru.abstractmenus.api.Rule; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Types; import ru.abstractmenus.data.rules.logical.RuleAnd; import ru.abstractmenus.util.StringUtil; @@ -122,7 +122,7 @@ public static void deserializeAction(String key, ConfigNode node, Actions action if (Actions.isReserved(key)) return; - Class type = Types.getActionType(key); + Class type = AbstractMenusApi.get().actions().get(key); if (type != null) { actions.add(node.getValue(type)); diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPropertySet.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPropertySet.java index e8f7e55..d2cb2eb 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPropertySet.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPropertySet.java @@ -5,11 +5,11 @@ import ru.abstractmenus.hocon.api.serialize.NodeSerializeException; import ru.abstractmenus.hocon.api.serialize.NodeSerializer; import org.bukkit.entity.Player; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.ItemProperty; import ru.abstractmenus.api.inventory.Menu; -import ru.abstractmenus.api.Types; import ru.abstractmenus.menu.item.InventoryItem; import ru.abstractmenus.api.inventory.slot.SlotIndex; import ru.abstractmenus.api.inventory.Slot; @@ -55,7 +55,7 @@ public ActionPropertySet deserialize(Class type, ConfigNode node) throws NodeSer for (Map.Entry entry : children.entrySet()) { String key = entry.getKey(); - Class token = Types.getItemPropertyType(key); + Class token = AbstractMenusApi.get().itemProperties().get(key); if (token != null) { propertyMap.put(key, entry.getValue().getValue(token)); diff --git a/plugin/src/main/java/ru/abstractmenus/data/properties/PropBindings.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropBindings.java index 6b07b64..a51bfcf 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/properties/PropBindings.java +++ b/plugin/src/main/java/ru/abstractmenus/data/properties/PropBindings.java @@ -8,10 +8,10 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.Rule; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.ItemProperty; -import ru.abstractmenus.api.Types; import ru.abstractmenus.menu.item.SimpleItem; import ru.abstractmenus.data.rules.logical.RuleAnd; @@ -86,7 +86,7 @@ public BindGroup deserialize(Class type, ConfigNode node) throws NodeSerializeEx if (Actions.isReserved(key)) continue; - Class token = Types.getItemPropertyType(key); + Class token = AbstractMenusApi.get().itemProperties().get(key); if (token != null) { properties.add(entry.getValue().getValue(token)); diff --git a/plugin/src/main/java/ru/abstractmenus/data/rules/logical/RulesGroup.java b/plugin/src/main/java/ru/abstractmenus/data/rules/logical/RulesGroup.java index f4a59e2..b7a8194 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/rules/logical/RulesGroup.java +++ b/plugin/src/main/java/ru/abstractmenus/data/rules/logical/RulesGroup.java @@ -6,10 +6,10 @@ import ru.abstractmenus.hocon.api.serialize.NodeSerializeException; import ru.abstractmenus.hocon.api.serialize.NodeSerializer; import org.bukkit.entity.Player; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.Rule; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; -import ru.abstractmenus.api.Types; import ru.abstractmenus.util.StringUtil; import java.util.LinkedList; @@ -81,7 +81,7 @@ private Rule getSingleRule(String key, ConfigNode node) throws NodeSerializeExce isNot = true; } - Class type = Types.getRuleType(key); + Class type = AbstractMenusApi.get().rules().get(key); if (type != null) { Rule rule = node.getValue(type); diff --git a/plugin/src/main/java/ru/abstractmenus/placeholders/hooks/ActivatorPlaceholders.java b/plugin/src/main/java/ru/abstractmenus/placeholders/hooks/ActivatorPlaceholders.java index 6416433..a6c3893 100644 --- a/plugin/src/main/java/ru/abstractmenus/placeholders/hooks/ActivatorPlaceholders.java +++ b/plugin/src/main/java/ru/abstractmenus/placeholders/hooks/ActivatorPlaceholders.java @@ -1,8 +1,8 @@ package ru.abstractmenus.placeholders.hooks; import org.bukkit.entity.Player; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.Activator; -import ru.abstractmenus.api.Types; import ru.abstractmenus.api.ValueExtractor; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.placeholders.PlaceholderHook; @@ -18,7 +18,7 @@ public String replace(String placeholder, Player player) { Activator activator = menu.getActivatedBy().get(); if (placeholder.equalsIgnoreCase("name")) - return Types.getActivatorName(activator.getClass()); + return AbstractMenusApi.get().activators().name(activator.getClass()); if (menu.getContext().isPresent()) { ValueExtractor extractor = activator.getValueExtractor(); diff --git a/plugin/src/main/java/ru/abstractmenus/serializers/ItemSerializer.java b/plugin/src/main/java/ru/abstractmenus/serializers/ItemSerializer.java index e1fd68f..551fc48 100644 --- a/plugin/src/main/java/ru/abstractmenus/serializers/ItemSerializer.java +++ b/plugin/src/main/java/ru/abstractmenus/serializers/ItemSerializer.java @@ -15,7 +15,7 @@ import ru.abstractmenus.menu.item.InventoryItem; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.ItemProperty; -import ru.abstractmenus.api.Types; +import ru.abstractmenus.api.AbstractMenusApi; import java.util.*; @@ -67,7 +67,7 @@ public Item deserialize(Class type, ConfigNode node) throws NodeSerializeE for (Map.Entry entry : children.entrySet()) { String key = entry.getKey(); - Class token = Types.getItemPropertyType(key); + Class token = AbstractMenusApi.get().itemProperties().get(key); if (token != null) { item.addProperty(key, entry.getValue().getValue(token)); diff --git a/plugin/src/main/java/ru/abstractmenus/serializers/Serializers.java b/plugin/src/main/java/ru/abstractmenus/serializers/Serializers.java index 20026d0..a5e6674 100644 --- a/plugin/src/main/java/ru/abstractmenus/serializers/Serializers.java +++ b/plugin/src/main/java/ru/abstractmenus/serializers/Serializers.java @@ -7,7 +7,7 @@ import org.bukkit.inventory.ShapedRecipe; import org.bukkit.plugin.Plugin; import org.bukkit.potion.PotionEffect; -import ru.abstractmenus.api.Types; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.command.args.*; import ru.abstractmenus.data.Actions; import ru.abstractmenus.data.BannerData; @@ -39,7 +39,7 @@ private Serializers() { } public static void init(Plugin plugin) { - NodeSerializers serializers = Types.serializers(); + NodeSerializers serializers = AbstractMenusApi.get().serializers(); serializers.register(TypeBool.class, new TypeBool.Serializer()); serializers.register(TypeByte.class, new TypeByte.Serializer()); diff --git a/plugin/src/main/java/ru/abstractmenus/serializers/menu/BaseMenuSerializer.java b/plugin/src/main/java/ru/abstractmenus/serializers/menu/BaseMenuSerializer.java index eeec369..f9ba1ec 100644 --- a/plugin/src/main/java/ru/abstractmenus/serializers/menu/BaseMenuSerializer.java +++ b/plugin/src/main/java/ru/abstractmenus/serializers/menu/BaseMenuSerializer.java @@ -8,9 +8,9 @@ import ru.abstractmenus.hocon.api.ConfigNode; import ru.abstractmenus.hocon.api.serialize.NodeSerializeException; import ru.abstractmenus.hocon.api.serialize.NodeSerializer; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.Activator; import ru.abstractmenus.api.inventory.Menu; -import ru.abstractmenus.api.Types; import ru.abstractmenus.menu.AbstractMenu; import ru.abstractmenus.menu.SimpleMenu; import ru.abstractmenus.menu.animated.AnimatedMenu; @@ -130,7 +130,7 @@ private List getActivators(ConfigNode node) throws NodeSerializeExcep for (Map.Entry entry : activators.entrySet()) { String key = entry.getKey(); - Class type = Types.getActivator(key); + Class type = AbstractMenusApi.get().activators().get(key); if (type != null) { Activator activator = entry.getValue().getValue(type); diff --git a/plugin/src/main/java/ru/abstractmenus/serializers/menu/GeneratedMenuSerializer.java b/plugin/src/main/java/ru/abstractmenus/serializers/menu/GeneratedMenuSerializer.java index 36b54a5..4e0b7f5 100644 --- a/plugin/src/main/java/ru/abstractmenus/serializers/menu/GeneratedMenuSerializer.java +++ b/plugin/src/main/java/ru/abstractmenus/serializers/menu/GeneratedMenuSerializer.java @@ -1,11 +1,11 @@ package ru.abstractmenus.serializers.menu; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.Catalog; import ru.abstractmenus.hocon.api.ConfigNode; import ru.abstractmenus.hocon.api.serialize.NodeSerializeException; import ru.abstractmenus.menu.generated.GeneratedMenu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Types; import ru.abstractmenus.menu.generated.Matrix; public class GeneratedMenuSerializer implements MenuSerializer { @@ -15,7 +15,7 @@ public GeneratedMenu deserialize(ConfigNode node, String title, int size) throws GeneratedMenu menu = new GeneratedMenu(title, size); String catalogType = node.node("catalog", "type").getString(); - Class> catalogToken = Types.getCatalogType(catalogType); + Class> catalogToken = AbstractMenusApi.get().catalogs().get(catalogType); if (catalogToken != null) { menu.setCatalog(node.node("catalog").getValue(catalogToken)); diff --git a/plugin/src/main/java/ru/abstractmenus/services/MenuManager.java b/plugin/src/main/java/ru/abstractmenus/services/MenuManager.java index d55a78a..b8d9d50 100644 --- a/plugin/src/main/java/ru/abstractmenus/services/MenuManager.java +++ b/plugin/src/main/java/ru/abstractmenus/services/MenuManager.java @@ -6,9 +6,9 @@ import org.bukkit.plugin.Plugin; import ru.abstractmenus.AbstractMenus; import ru.abstractmenus.MainConfig; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.Activator; import ru.abstractmenus.api.inventory.Menu; -import ru.abstractmenus.api.Types; import ru.abstractmenus.command.Command; import ru.abstractmenus.data.actions.ActionInputChat; import ru.abstractmenus.data.activators.OpenCommand; @@ -212,7 +212,7 @@ private int loadFile(Path file) { try { conf = ConfigurationLoader.builder() .source(ConfigSources.path(file)) - .serializers(Types.serializers()) + .serializers(AbstractMenusApi.get().serializers()) .build() .load(); } catch (Throwable t) { @@ -364,7 +364,7 @@ private void loadExampleMenus() throws Exception { ConfigurationLoader.builder() .source(ConfigSources.resource("/menu.conf", plugin) .copyTo(menuFolder)) - .serializers(Types.serializers()) + .serializers(AbstractMenusApi.get().serializers()) .build() .load(); @@ -381,7 +381,7 @@ private void loadExampleMenus() throws Exception { ConfigurationLoader.builder() .source(ConfigSources.resource(animMenuPath, plugin) .copyTo(menuFolder)) - .serializers(Types.serializers()) + .serializers(AbstractMenusApi.get().serializers()) .build() .load(); } From f39dae409e33d8326933e2b2b36b64d0727b40f7 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Wed, 22 Apr 2026 21:37:23 +0500 Subject: [PATCH 29/71] refactor(core): introduce CoreItemPropertyKeys constants holder Replaces the "bindings" literal inlined during the CoreExtension cutover with a typed constant referenced from both the registration bundle and the single external consumer (actionClear). Keeps the registration and consumption sites in sync at compile time. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../core/CoreItemPropertyKeys.java | 20 +++++++++++++++++++ .../core/CoreItemPropsBundle.java | 2 +- .../data/actions/ActionPropertySet.java | 3 ++- 3 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 plugin/src/main/java/ru/abstractmenus/core/CoreItemPropertyKeys.java diff --git a/plugin/src/main/java/ru/abstractmenus/core/CoreItemPropertyKeys.java b/plugin/src/main/java/ru/abstractmenus/core/CoreItemPropertyKeys.java new file mode 100644 index 0000000..205be89 --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/core/CoreItemPropertyKeys.java @@ -0,0 +1,20 @@ +package ru.abstractmenus.core; + +/** + * HOCON-visible keys for core item properties. Centralised so that the + * registration site ({@link CoreItemPropsBundle}) and consumers elsewhere + * in the plugin reference the same symbol rather than duplicating string + * literals. + * + *

Only those keys that are referenced by name outside + * {@link CoreItemPropsBundle} need a constant here. The rest stay as + * literals at their single point of use. + */ +public final class CoreItemPropertyKeys { + + private CoreItemPropertyKeys() { + } + + /** The {@code bindings} property — referenced by {@code actionClear}. */ + public static final String BINDINGS = "bindings"; +} diff --git a/plugin/src/main/java/ru/abstractmenus/core/CoreItemPropsBundle.java b/plugin/src/main/java/ru/abstractmenus/core/CoreItemPropsBundle.java index dc90e72..8c4a65a 100644 --- a/plugin/src/main/java/ru/abstractmenus/core/CoreItemPropsBundle.java +++ b/plugin/src/main/java/ru/abstractmenus/core/CoreItemPropsBundle.java @@ -72,7 +72,7 @@ void register(AbstractMenusApi api, MenuExtension owner) { api.itemProperties().register("recipes", PropKnowledgeBook.class, new PropKnowledgeBook.Serializer(), owner); api.itemProperties().register("damage", PropDamage.class, new PropDamage.Serializer(), owner); api.itemProperties().register("nbt", PropNbt.class, new PropNbt.Serializer(), owner); - api.itemProperties().register("bindings", PropBindings.class, new PropBindings.Serializer(), owner); + api.itemProperties().register(CoreItemPropertyKeys.BINDINGS, PropBindings.class, new PropBindings.Serializer(), owner); api.itemProperties().register("nameLight", PropNameLight.class, new PropNameLight.Serializer(), owner); api.itemProperties().register("loreLight", PropLoreLight.class, new PropLoreLight.Serializer(), owner); diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPropertySet.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPropertySet.java index d2cb2eb..9d57b82 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPropertySet.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPropertySet.java @@ -13,6 +13,7 @@ import ru.abstractmenus.menu.item.InventoryItem; import ru.abstractmenus.api.inventory.slot.SlotIndex; import ru.abstractmenus.api.inventory.Slot; +import ru.abstractmenus.core.CoreItemPropertyKeys; import java.util.*; @@ -39,7 +40,7 @@ public void activate(Player player, Menu menu, Item clickedItem) { slot.getSlots(index -> { // Just modifying ItemStack from inventory won't work because remProperty breaks it Item item = menu.getItem(index); - item.removeProperty("bindings"); + item.removeProperty(CoreItemPropertyKeys.BINDINGS); item.setProperties(properties); menu.setItem(SlotIndex.of(index), item, player); }); From f7e43851667ad65b4dcc412e4cd429e6780a703a Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Wed, 22 Apr 2026 22:12:56 +0500 Subject: [PATCH 30/71] =?UTF-8?q?refactor(api)!:=20remove=20Types=20?= =?UTF-8?q?=E2=80=94=20replaced=20by=20AbstractMenusApi=20+=20TypeRegistry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 (1M context) --- .../main/java/ru/abstractmenus/api/Types.java | 180 ------------------ 1 file changed, 180 deletions(-) delete mode 100644 api/src/main/java/ru/abstractmenus/api/Types.java diff --git a/api/src/main/java/ru/abstractmenus/api/Types.java b/api/src/main/java/ru/abstractmenus/api/Types.java deleted file mode 100644 index bed0a7e..0000000 --- a/api/src/main/java/ru/abstractmenus/api/Types.java +++ /dev/null @@ -1,180 +0,0 @@ -package ru.abstractmenus.api; - -import com.google.common.collect.BiMap; -import com.google.common.collect.HashBiMap; -import ru.abstractmenus.api.inventory.ItemProperty; -import ru.abstractmenus.hocon.api.serialize.NodeSerializer; -import ru.abstractmenus.hocon.api.serialize.NodeSerializers; - -/** - * Serializers provider. All data types for menus registering here - */ -public final class Types { - - private static final NodeSerializers SERIALIZERS = NodeSerializers.defaults(); - private static final BiMap> ACTION_TYPES = HashBiMap.create(); - private static final BiMap> RULE_TYPES = HashBiMap.create(); - private static final BiMap> ACTIVATOR_TYPES = HashBiMap.create(); - private static final BiMap> ITEM_PROPERTIES_TYPES = HashBiMap.create(); - private static final BiMap>> CATALOG_TYPES = HashBiMap.create(); - - private Types() { } - - /** - * Global serializers collection - * @return Serializers collection - */ - public static NodeSerializers serializers() { - return SERIALIZERS; - } - - /** - * Get action name by type - * @param type Action type - * @return Action name or null if not found - */ - public static String getActionName(Class type) { - return ACTION_TYPES.inverse().get(type); - } - - /** - * Get rule name by type - * @param type Rule type - * @return Rule name or null if not found - */ - public static String getRuleName(Class type) { - return RULE_TYPES.inverse().get(type); - } - - /** - * Get activator name by type - * @param type Activator type - * @return Activator name or null if not found - */ - public static String getActivatorName(Class type) { - return ACTIVATOR_TYPES.inverse().get(type); - } - - /** - * Get item property name by type - * @param type Item property type - * @return Item property name or null if not found - */ - public static String getItemPropertyName(Class type) { - return ITEM_PROPERTIES_TYPES.inverse().get(type); - } - - /** - * Get catalog name by type - * @param type Catalog type - * @return Catalog name or null if not found - */ - public static String getCatalogName(Class type) { - return CATALOG_TYPES.inverse().get(type); - } - - /** - * Register new action - * @param key Key of the action. This key uses in menu file - * @param token Type of the action - * @param serializer Serializer of the action - * @param Type of the action - */ - public static void registerAction(String key, Class token, NodeSerializer serializer){ - serializers().register(token, serializer); - ACTION_TYPES.put(key.toLowerCase(), token); - } - - /** - * Get token to deserialize action from menu file - * @param key Action key - * @return Found TypeToken of the registered action of null if token not found - */ - public static Class getActionType(String key){ - return ACTION_TYPES.get(key.toLowerCase()); - } - - /** - * Register new rule - * @param key Key of the rule. This key uses in menu file - * @param token Type of the rule - * @param serializer Serializer of the rule - * @param Type of the rule - */ - public static void registerRule(String key, Class token, NodeSerializer serializer){ - serializers().register(token, serializer); - RULE_TYPES.put(key.toLowerCase(), token); - } - - /** - * Get token to deserialize rule from menu file - * @param key Rule key - * @return Found TypeToken of the registered rule of null if token not found - */ - public static Class getRuleType(String key){ - return RULE_TYPES.get(key.toLowerCase()); - } - - /** - * Register new menu activator - * @param key Key of the activator. This key uses in menu file - * @param token Type of the activator - * @param serializer Serializer of the activator - * @param Type of the activator - */ - public static void registerActivator(String key, Class token, NodeSerializer serializer){ - serializers().register(token, serializer); - ACTIVATOR_TYPES.put(key.toLowerCase(), token); - } - - /** - * Get token to deserialize activator from menu file - * @param key Activator key - * @return Found TypeToken of the registered activator of null if token not found - */ - public static Class getActivator(String key){ - return ACTIVATOR_TYPES.get(key.toLowerCase()); - } - - /** - * Register new item property - * @param key Key of the property. This key uses in menu file - * @param token Type of the property - * @param serializer Serializer of the property - * @param Type of the property - */ - public static void registerItemProperty(String key, Class token, NodeSerializer serializer){ - serializers().register(token, serializer); - ITEM_PROPERTIES_TYPES.put(key.toLowerCase(), token); - } - - /** - * Get token to deserialize item property from menu file - * @param key Item property key - * @return Found TypeToken of the registered item property of null if token not found - */ - public static Class getItemPropertyType(String key){ - return ITEM_PROPERTIES_TYPES.get(key.toLowerCase()); - } - - /** - * Register new catalog - * @param key Key of the catalog. This key uses in menu file - * @param token Type of the catalog - * @param serializer Serializer of the catalog - * @param Type of the catalog - */ - public static > void registerCatalog(String key, Class token, NodeSerializer serializer){ - serializers().register(token, serializer); - CATALOG_TYPES.put(key.toLowerCase(), token); - } - - /** - * Get token to deserialize catalog from menu file - * @param key Catalog key - * @return Found TypeToken of the registered catalog of null if token not found - */ - public static Class> getCatalogType(String key){ - return CATALOG_TYPES.get(key.toLowerCase()); - } -} From e046cba5a31456c20cdeaaf8967a3921b7f783fc Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Wed, 22 Apr 2026 22:33:44 +0500 Subject: [PATCH 31/71] refactor(api)!: remove AbstractMenusPlugin + AbstractMenusProvider Callers now use AbstractMenusApi.get() (ServicesManager-backed) instead of the old static accessors. AbstractMenus keeps its concrete getPlugin/ getVariableManager/loadMenus/openMenu/getOpenedMenu methods for internal use but no longer declares them via an API interface. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../api/AbstractMenusPlugin.java | 55 ------------------- .../api/AbstractMenusProvider.java | 28 ---------- .../java/ru/abstractmenus/api/Activator.java | 2 +- .../java/ru/abstractmenus/AbstractMenus.java | 11 +--- 4 files changed, 2 insertions(+), 94 deletions(-) delete mode 100644 api/src/main/java/ru/abstractmenus/api/AbstractMenusPlugin.java delete mode 100644 api/src/main/java/ru/abstractmenus/api/AbstractMenusProvider.java diff --git a/api/src/main/java/ru/abstractmenus/api/AbstractMenusPlugin.java b/api/src/main/java/ru/abstractmenus/api/AbstractMenusPlugin.java deleted file mode 100644 index 3d5d065..0000000 --- a/api/src/main/java/ru/abstractmenus/api/AbstractMenusPlugin.java +++ /dev/null @@ -1,55 +0,0 @@ -package ru.abstractmenus.api; - -import org.bukkit.entity.Player; -import org.bukkit.plugin.Plugin; -import ru.abstractmenus.api.inventory.Menu; -import ru.abstractmenus.api.variables.VariableManager; - -import java.util.Optional; - -/** - * Base plugin interface - */ -public interface AbstractMenusPlugin { - - /** - * Get plugin instance - * @return Plugin instance - */ - Plugin getPlugin(); - - /** - * Get variables manager - * @return Variables manager instance - */ - VariableManager getVariableManager(); - - /** - * Reload all menus - */ - void loadMenus(); - - /** - * Open menu for a player with activator and context - * @param activator Activator which caused opening. Might be null - * @param ctx Opening context (object that causes opening) - * @param player Menu viewer - * @param menu Menu to open - */ - void openMenu(Activator activator, Object ctx, Player player, Menu menu); - - /** - * Open menu for a player - * @param player Menu viewer - * @param menu Menu to open - */ - void openMenu(Player player, Menu menu); - - /** - * Get opened menu by player who opened this menu - * @param player Menu viewer - * @return Found menu in Optional wrapper or Optional.EMPTY otherwise - */ - Optional

getOpenedMenu(Player player); - -} diff --git a/api/src/main/java/ru/abstractmenus/api/AbstractMenusProvider.java b/api/src/main/java/ru/abstractmenus/api/AbstractMenusProvider.java deleted file mode 100644 index 78ceec2..0000000 --- a/api/src/main/java/ru/abstractmenus/api/AbstractMenusProvider.java +++ /dev/null @@ -1,28 +0,0 @@ -package ru.abstractmenus.api; - -/** - * Plugin provider. Provides access to plugin instance - */ -public final class AbstractMenusProvider { - - private static AbstractMenusPlugin plugin; - - private AbstractMenusProvider(){} - - /** - * Initialize plugin instance - * @param plug Plugin instance - */ - public static void init(AbstractMenusPlugin plug){ - plugin = plug; - } - - /** - * Get AbstractMenus plugin instance - * @return Plugin instance - */ - public static AbstractMenusPlugin get(){ - return plugin; - } - -} diff --git a/api/src/main/java/ru/abstractmenus/api/Activator.java b/api/src/main/java/ru/abstractmenus/api/Activator.java index c159242..9657857 100644 --- a/api/src/main/java/ru/abstractmenus/api/Activator.java +++ b/api/src/main/java/ru/abstractmenus/api/Activator.java @@ -29,7 +29,7 @@ public void setTargetMenu(Menu menu) { * @param player Player to open menu */ protected void openMenu(Object ctx, Player player) { - AbstractMenusProvider.get().openMenu(this, ctx, player, menu); + AbstractMenusApi.get().openMenu(this, ctx, player, menu); } /** diff --git a/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java b/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java index 6924703..bc9652f 100644 --- a/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java +++ b/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java @@ -61,7 +61,7 @@ import java.util.Optional; @Getter -public final class AbstractMenus extends JavaPlugin implements AbstractMenusPlugin { +public final class AbstractMenus extends JavaPlugin { private static AbstractMenus instance; @@ -74,28 +74,22 @@ public final class AbstractMenus extends JavaPlugin implements AbstractMenusPlug @Setter public boolean isProxyMode; - @Override public Plugin getPlugin() { return this; } public AbstractMenusApi getApi() { return api; } - @Override public VariableManager getVariableManager() { return VariableManagerImpl.instance(); } - @Override public Optional getOpenedMenu(Player player) { return Optional.ofNullable(MenuManager.instance().getOpenedMenu(player)); } @Override public void onLoad() { - AbstractMenusProvider.init(this); - getServer().getServicesManager() - .register(AbstractMenusPlugin.class, this, this, ServicePriority.Normal); } @Override @@ -173,7 +167,6 @@ public void onEnable() { } } - @Override public void loadMenus() { try { MenuManager.instance().loadMenus(); @@ -183,12 +176,10 @@ public void loadMenus() { } } - @Override public void openMenu(Player player, Menu menu) { MenuManager.instance().openMenu(player, menu); } - @Override public void openMenu(Activator activator, Object ctx, Player player, Menu menu) { MenuManager.instance().openMenu(activator, ctx, player, menu); } From 3ce550d13e4faa18f704386b2fcc85b5e9e35187 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Wed, 22 Apr 2026 22:53:02 +0500 Subject: [PATCH 32/71] =?UTF-8?q?test(core):=20CoreExtensionTest=20?= =?UTF-8?q?=E2=80=94=20verify=20registrations=20after=20onEnable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Boots CoreExtension via a lightweight StubApi (TypeRegistryImpl-backed) and asserts that the five bundles register the expected 65/28/32/14/6 core types across actions, rules, item-properties, activators, and catalogs. Uses Mockito mockStatic(Bukkit.class) so checkDependency() does not NPE; without Citizens/WorldGuard the activator count is 14 (not 17 in production). Avoids the MockBukkit + Paper 1.21.11 registry bootstrap incompatibility that keeps TestPluginLifecycle disabled. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../abstractmenus/core/CoreExtensionTest.java | 168 ++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 plugin/src/test/java/ru/abstractmenus/core/CoreExtensionTest.java diff --git a/plugin/src/test/java/ru/abstractmenus/core/CoreExtensionTest.java b/plugin/src/test/java/ru/abstractmenus/core/CoreExtensionTest.java new file mode 100644 index 0000000..fce7108 --- /dev/null +++ b/plugin/src/test/java/ru/abstractmenus/core/CoreExtensionTest.java @@ -0,0 +1,168 @@ +package ru.abstractmenus.core; + +import org.bukkit.Bukkit; +import org.bukkit.Server; +import org.bukkit.plugin.PluginManager; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import ru.abstractmenus.api.AbstractMenusApi; +import ru.abstractmenus.api.Action; +import ru.abstractmenus.api.Activator; +import ru.abstractmenus.api.Catalog; +import ru.abstractmenus.api.MenuExtension; +import ru.abstractmenus.api.Rule; +import ru.abstractmenus.api.TypeRegistry; +import ru.abstractmenus.api.TypeRegistryImpl; +import ru.abstractmenus.api.inventory.ItemProperty; +import ru.abstractmenus.api.inventory.Menu; +import ru.abstractmenus.api.variables.VariableManager; +import ru.abstractmenus.hocon.api.serialize.NodeSerializers; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +/** + * Verifies that {@link CoreExtension#onEnable(AbstractMenusApi)} registers the + * expected number of core types into the five registries. + * + *

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

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

getOpenedMenu(org.bukkit.entity.Player p) { throw new UnsupportedOperationException(); } + @Override public String apiVersion() { return "test"; } + } + + /** Minimal extension for test purposes (not used directly here). */ + private static final class DummyExtension implements MenuExtension { + @Override public String name() { return "TestExtension"; } + @Override public void onEnable(AbstractMenusApi api) {} + } +} From 1a3a4511261e0bb9a12ae8633fb71f9bd7650f76 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Wed, 22 Apr 2026 23:13:22 +0500 Subject: [PATCH 33/71] feat(addon): AddonConf + HOCON parser (8 tests) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Immutable metadata record parsed from addon.conf — required fields (name, version, main) + optional (authors, description, targetApiVersion, addon/ plugin/pluginSoft dependencies). Uses the in-tree HOCON parser. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../ru/abstractmenus/addon/AddonConf.java | 125 ++++++++++++++++++ .../addon/AddonConfParseException.java | 13 ++ .../ru/abstractmenus/addon/AddonConfTest.java | 111 ++++++++++++++++ 3 files changed, 249 insertions(+) create mode 100644 plugin/src/main/java/ru/abstractmenus/addon/AddonConf.java create mode 100644 plugin/src/main/java/ru/abstractmenus/addon/AddonConfParseException.java create mode 100644 plugin/src/test/java/ru/abstractmenus/addon/AddonConfTest.java diff --git a/plugin/src/main/java/ru/abstractmenus/addon/AddonConf.java b/plugin/src/main/java/ru/abstractmenus/addon/AddonConf.java new file mode 100644 index 0000000..455a8e8 --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/addon/AddonConf.java @@ -0,0 +1,125 @@ +package ru.abstractmenus.addon; + +import ru.abstractmenus.hocon.api.ConfigNode; +import ru.abstractmenus.hocon.api.ConfigurationLoader; +import ru.abstractmenus.hocon.api.source.ConfigSources; + +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; +import java.util.List; + +/** + * Immutable metadata parsed from an addon's {@code addon.conf} file. Use + * {@link #parse(String)} to construct from HOCON text. + * + *

Three of the nine fields are required ({@code name}, {@code version}, + * {@code main}); the rest default to empty or absent. + */ +public record AddonConf( + String name, + String version, + String main, + List authors, + String description, + String targetApiVersion, + List addonDependencies, + List pluginDependencies, + List pluginSoftDependencies +) { + + /** + * Parse HOCON text into an AddonConf. + * + * @param hocon raw text content of an {@code addon.conf} file + * @return parsed conf + * @throws AddonConfParseException if required fields are missing, fields + * have the wrong type, or the HOCON itself is malformed + */ + public static AddonConf parse(String hocon) { + ConfigNode root; + try { + ByteArrayInputStream in = new ByteArrayInputStream( + hocon.getBytes(StandardCharsets.UTF_8)); + root = ConfigurationLoader.builder() + .source(ConfigSources.inputStream("addon.conf", in)) + .build() + .load(); + } catch (Exception e) { + throw new AddonConfParseException("Failed to parse addon.conf: " + e.getMessage(), e); + } + + String name = requireString(root, "name"); + String version = requireString(root, "version"); + String main = requireString(root, "main"); + + List authors = optionalStringList(root, "authors"); + String description = root.node("description").getString(""); + String targetApiVersion = optionalString(root, "targetApiVersion"); + List addonDependencies = optionalStringList(root, "addonDependencies"); + List pluginDependencies = optionalStringList(root, "pluginDependencies"); + List pluginSoftDependencies = optionalStringList(root, "pluginSoftDependencies"); + + return new AddonConf( + name, version, main, + authors, description, targetApiVersion, + addonDependencies, pluginDependencies, pluginSoftDependencies + ); + } + + // ------------------------------------------------------------------------- + // Helpers + // ------------------------------------------------------------------------- + + /** + * Returns the string value of a required field. + * + * @throws AddonConfParseException if the field is absent, null, or blank + */ + private static String requireString(ConfigNode root, String field) { + ConfigNode node = root.node(field); + if (node.isNull()) { + throw new AddonConfParseException( + "Required field '" + field + "' is missing or blank"); + } + String value = node.getString(null); + if (value == null || value.isBlank()) { + throw new AddonConfParseException( + "Required field '" + field + "' is missing or blank"); + } + return value; + } + + /** + * Returns an optional string value, or {@code null} if absent. + */ + private static String optionalString(ConfigNode root, String field) { + ConfigNode node = root.node(field); + if (node.isNull()) { + return null; + } + return node.getString(null); + } + + /** + * Returns an optional list of strings. Accepts either a HOCON list or a + * single string (auto-wrapped into a one-element list). Returns + * {@link List#of()} if the field is absent. + */ + private static List optionalStringList(ConfigNode root, String field) { + ConfigNode node = root.node(field); + if (node.isNull()) { + return List.of(); + } + // Single-string scalar: wrap in a one-element list. + if (node.isPrimitive()) { + String value = node.getString(null); + return value != null ? List.of(value) : List.of(); + } + try { + return List.copyOf(node.getList(String.class)); + } catch (Exception e) { + throw new AddonConfParseException( + "Field '" + field + "' must be a string or list of strings: " + e.getMessage(), e); + } + } +} diff --git a/plugin/src/main/java/ru/abstractmenus/addon/AddonConfParseException.java b/plugin/src/main/java/ru/abstractmenus/addon/AddonConfParseException.java new file mode 100644 index 0000000..5dc9091 --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/addon/AddonConfParseException.java @@ -0,0 +1,13 @@ +package ru.abstractmenus.addon; + +/** Unchecked exception for malformed or incomplete {@code addon.conf} files. */ +public class AddonConfParseException extends RuntimeException { + + public AddonConfParseException(String message) { + super(message); + } + + public AddonConfParseException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/plugin/src/test/java/ru/abstractmenus/addon/AddonConfTest.java b/plugin/src/test/java/ru/abstractmenus/addon/AddonConfTest.java new file mode 100644 index 0000000..d8b04af --- /dev/null +++ b/plugin/src/test/java/ru/abstractmenus/addon/AddonConfTest.java @@ -0,0 +1,111 @@ +package ru.abstractmenus.addon; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class AddonConfTest { + + @Test + void parse_minimal_requiredOnly() { + String hocon = """ + name = "MyAddon" + version = "1.0.0" + main = "com.example.MyAddon" + """; + AddonConf c = AddonConf.parse(hocon); + + assertEquals("MyAddon", c.name()); + assertEquals("1.0.0", c.version()); + assertEquals("com.example.MyAddon", c.main()); + assertEquals(List.of(), c.authors()); + assertEquals("", c.description()); + assertNull(c.targetApiVersion()); + assertEquals(List.of(), c.addonDependencies()); + assertEquals(List.of(), c.pluginDependencies()); + assertEquals(List.of(), c.pluginSoftDependencies()); + } + + @Test + void parse_full() { + String hocon = """ + name = "MyAddon" + version = "1.2.3" + main = "com.example.MyAddon" + authors = ["alice", "bob"] + description = "Does things." + targetApiVersion = "2.0.0" + addonDependencies = ["common-utils"] + pluginDependencies = ["NBT-API"] + pluginSoftDependencies = ["WorldGuard"] + """; + AddonConf c = AddonConf.parse(hocon); + + assertEquals(List.of("alice", "bob"), c.authors()); + assertEquals("Does things.", c.description()); + assertEquals("2.0.0", c.targetApiVersion()); + assertEquals(List.of("common-utils"), c.addonDependencies()); + assertEquals(List.of("NBT-API"), c.pluginDependencies()); + assertEquals(List.of("WorldGuard"), c.pluginSoftDependencies()); + } + + @Test + void parse_missingName_throws() { + String hocon = """ + version = "1.0.0" + main = "com.example.MyAddon" + """; + AddonConfParseException ex = assertThrows(AddonConfParseException.class, + () -> AddonConf.parse(hocon)); + assertTrue(ex.getMessage().toLowerCase().contains("name")); + } + + @Test + void parse_missingVersion_throws() { + String hocon = """ + name = "MyAddon" + main = "com.example.MyAddon" + """; + assertThrows(AddonConfParseException.class, () -> AddonConf.parse(hocon)); + } + + @Test + void parse_missingMain_throws() { + String hocon = """ + name = "MyAddon" + version = "1.0.0" + """; + assertThrows(AddonConfParseException.class, () -> AddonConf.parse(hocon)); + } + + @Test + void parse_blankName_throws() { + String hocon = """ + name = " " + version = "1.0.0" + main = "com.example.MyAddon" + """; + assertThrows(AddonConfParseException.class, () -> AddonConf.parse(hocon)); + } + + @Test + void parse_malformedHocon_throws() { + String hocon = "this is not { valid hocon"; + assertThrows(AddonConfParseException.class, () -> AddonConf.parse(hocon)); + } + + @Test + void listFields_acceptSingleElementInsteadOfList() { + // HOCON allows either a single string or a list; both should parse. + String hocon = """ + name = "MyAddon" + version = "1.0.0" + main = "com.example.MyAddon" + authors = "solo" + """; + AddonConf c = AddonConf.parse(hocon); + assertEquals(List.of("solo"), c.authors()); + } +} From ad32c6c47a46bab7512ebe70e37de1f01130e821 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Wed, 22 Apr 2026 23:15:49 +0500 Subject: [PATCH 34/71] =?UTF-8?q?feat(addon):=20AddonDependencyGraph=20?= =?UTF-8?q?=E2=80=94=20DFS=20topsort=20+=20cycle=20detection=20(7=20tests)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 (1M context) --- .../addon/AddonDependencyCycleException.java | 6 ++ .../addon/AddonDependencyException.java | 6 ++ .../addon/AddonDependencyGraph.java | 85 ++++++++++++++++++ .../addon/AddonDependencyGraphTest.java | 87 +++++++++++++++++++ 4 files changed, 184 insertions(+) create mode 100644 plugin/src/main/java/ru/abstractmenus/addon/AddonDependencyCycleException.java create mode 100644 plugin/src/main/java/ru/abstractmenus/addon/AddonDependencyException.java create mode 100644 plugin/src/main/java/ru/abstractmenus/addon/AddonDependencyGraph.java create mode 100644 plugin/src/test/java/ru/abstractmenus/addon/AddonDependencyGraphTest.java diff --git a/plugin/src/main/java/ru/abstractmenus/addon/AddonDependencyCycleException.java b/plugin/src/main/java/ru/abstractmenus/addon/AddonDependencyCycleException.java new file mode 100644 index 0000000..d0506b0 --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/addon/AddonDependencyCycleException.java @@ -0,0 +1,6 @@ +package ru.abstractmenus.addon; + +/** Raised when {@code addonDependencies} form a cycle. */ +public class AddonDependencyCycleException extends AddonDependencyException { + public AddonDependencyCycleException(String message) { super(message); } +} diff --git a/plugin/src/main/java/ru/abstractmenus/addon/AddonDependencyException.java b/plugin/src/main/java/ru/abstractmenus/addon/AddonDependencyException.java new file mode 100644 index 0000000..ac899e4 --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/addon/AddonDependencyException.java @@ -0,0 +1,6 @@ +package ru.abstractmenus.addon; + +/** Raised when an addon declares a dependency that isn't satisfiable. */ +public class AddonDependencyException extends RuntimeException { + public AddonDependencyException(String message) { super(message); } +} diff --git a/plugin/src/main/java/ru/abstractmenus/addon/AddonDependencyGraph.java b/plugin/src/main/java/ru/abstractmenus/addon/AddonDependencyGraph.java new file mode 100644 index 0000000..e2ae328 --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/addon/AddonDependencyGraph.java @@ -0,0 +1,85 @@ +package ru.abstractmenus.addon; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Topological sort over an addon dependency graph. Pure function — no state. + * + *

DFS-based post-order traversal. Cycle detection uses the in-progress + * (temporary/grey) set to identify nodes revisited during an active DFS path. + */ +public final class AddonDependencyGraph { + + private AddonDependencyGraph() {} + + /** + * Sort addons into enabling order. + * + * @param dependencies map from addon name → list of names it depends on. + * Iteration order of the input map is preserved + * among independent addons (pass a + * {@link java.util.LinkedHashMap} for determinism). + * @return addon names in dependency-first order (deps before dependants) + * @throws AddonDependencyCycleException if a cycle is detected + * @throws AddonDependencyException if a declared dep refers to a + * name not present in the graph + */ + public static List topoSort(Map> dependencies) { + Set known = dependencies.keySet(); + + // Validation: every declared dep must exist in the graph. + for (Map.Entry> e : dependencies.entrySet()) { + for (String dep : e.getValue()) { + if (!known.contains(dep)) { + throw new AddonDependencyException( + "Addon '" + e.getKey() + "' depends on unknown addon '" + dep + "'"); + } + } + } + + Set permanent = new HashSet<>(); + Set temporary = new LinkedHashSet<>(); // order preserved for cycle message + List order = new ArrayList<>(); + + for (String node : dependencies.keySet()) { + if (!permanent.contains(node)) { + visit(node, dependencies, permanent, temporary, order); + } + } + + return order; + } + + private static void visit(String node, + Map> deps, + Set permanent, + Set temporary, + List order) { + if (permanent.contains(node)) return; + if (temporary.contains(node)) { + Deque cycle = new ArrayDeque<>(); + boolean found = false; + for (String n : temporary) { + if (n.equals(node)) found = true; + if (found) cycle.addLast(n); + } + cycle.addLast(node); + throw new AddonDependencyCycleException( + "Addon dependency cycle: " + String.join(" -> ", cycle)); + } + temporary.add(node); + for (String dep : deps.getOrDefault(node, List.of())) { + visit(dep, deps, permanent, temporary, order); + } + temporary.remove(node); + permanent.add(node); + order.add(node); + } +} diff --git a/plugin/src/test/java/ru/abstractmenus/addon/AddonDependencyGraphTest.java b/plugin/src/test/java/ru/abstractmenus/addon/AddonDependencyGraphTest.java new file mode 100644 index 0000000..a692054 --- /dev/null +++ b/plugin/src/test/java/ru/abstractmenus/addon/AddonDependencyGraphTest.java @@ -0,0 +1,87 @@ +package ru.abstractmenus.addon; + +import org.junit.jupiter.api.Test; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +class AddonDependencyGraphTest { + + @Test + void empty_returnsEmpty() { + List order = AddonDependencyGraph.topoSort(Map.of()); + assertEquals(List.of(), order); + } + + @Test + void noDependencies_preservesInsertionOrder() { + Map> deps = new LinkedHashMap<>(); + deps.put("a", List.of()); + deps.put("b", List.of()); + deps.put("c", List.of()); + + assertEquals(List.of("a", "b", "c"), AddonDependencyGraph.topoSort(deps)); + } + + @Test + void linearChain_sortsDeepToShallow() { + // c depends on b; b depends on a → enable order a, b, c + Map> deps = new LinkedHashMap<>(); + deps.put("c", List.of("b")); + deps.put("b", List.of("a")); + deps.put("a", List.of()); + + assertEquals(List.of("a", "b", "c"), AddonDependencyGraph.topoSort(deps)); + } + + @Test + void diamond_isHandled() { + // a + // / \ + // b c + // \ / + // d + Map> deps = new LinkedHashMap<>(); + deps.put("d", List.of("b", "c")); + deps.put("c", List.of("a")); + deps.put("b", List.of("a")); + deps.put("a", List.of()); + + List order = AddonDependencyGraph.topoSort(deps); + + assertTrue(order.indexOf("a") < order.indexOf("b")); + assertTrue(order.indexOf("a") < order.indexOf("c")); + assertTrue(order.indexOf("b") < order.indexOf("d")); + assertTrue(order.indexOf("c") < order.indexOf("d")); + } + + @Test + void selfCycle_throws() { + Map> deps = Map.of("a", List.of("a")); + AddonDependencyCycleException ex = assertThrows( + AddonDependencyCycleException.class, + () -> AddonDependencyGraph.topoSort(deps)); + assertTrue(ex.getMessage().contains("a")); + } + + @Test + void twoNodeCycle_throws() { + Map> deps = Map.of( + "a", List.of("b"), + "b", List.of("a")); + assertThrows(AddonDependencyCycleException.class, + () -> AddonDependencyGraph.topoSort(deps)); + } + + @Test + void unknownDependency_throws() { + Map> deps = Map.of("a", List.of("ghost")); + AddonDependencyException ex = assertThrows( + AddonDependencyException.class, + () -> AddonDependencyGraph.topoSort(deps)); + assertTrue(ex.getMessage().toLowerCase().contains("ghost")); + } +} From 0406f65e928c9e369513b9afc7ce3da4703eec47 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Thu, 23 Apr 2026 08:27:28 +0500 Subject: [PATCH 35/71] =?UTF-8?q?feat(addon):=20AddonClassLoader=20?= =?UTF-8?q?=E2=80=94=20parent-first=20for=20API/Bukkit/JDK,=20child-first?= =?UTF-8?q?=20rest=20(3=20tests)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 (1M context) --- .../abstractmenus/addon/AddonClassLoader.java | 74 +++++++++++++ .../addon/AddonClassLoaderTest.java | 100 ++++++++++++++++++ 2 files changed, 174 insertions(+) create mode 100644 plugin/src/main/java/ru/abstractmenus/addon/AddonClassLoader.java create mode 100644 plugin/src/test/java/ru/abstractmenus/addon/AddonClassLoaderTest.java diff --git a/plugin/src/main/java/ru/abstractmenus/addon/AddonClassLoader.java b/plugin/src/main/java/ru/abstractmenus/addon/AddonClassLoader.java new file mode 100644 index 0000000..df9a9b9 --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/addon/AddonClassLoader.java @@ -0,0 +1,74 @@ +package ru.abstractmenus.addon; + +import java.net.URL; +import java.net.URLClassLoader; + +/** + * Isolated classloader for an AM-loaded addon. + * + *

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

Child-first for everything else — the addon can ship + * its own shaded copies of Gson, Guava, HTTP clients, etc. without + * conflicting with what AbstractMenus (or other addons) shade. + */ +public final class AddonClassLoader extends URLClassLoader { + + static final String[] PARENT_FIRST_PREFIXES = { + "ru.abstractmenus.api.", + "org.bukkit.", + "io.papermc.", + "com.destroystokyo.paper.", + "net.kyori.adventure.", + "java.", + "javax.", + "jdk.", + "sun." + }; + + public AddonClassLoader(URL[] urls, ClassLoader parent) { + super(urls, parent); + } + + @Override + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + synchronized (getClassLoadingLock(name)) { + Class c = findLoadedClass(name); + if (c != null) { + if (resolve) resolveClass(c); + return c; + } + + // Parent-first packages — always delegate first. + for (String prefix : PARENT_FIRST_PREFIXES) { + if (name.startsWith(prefix)) { + try { + c = getParent().loadClass(name); + if (resolve) resolveClass(c); + return c; + } catch (ClassNotFoundException ignored) { + // fall through — parent didn't have it, try the jar + } + } + } + + // Child-first: try our own URLs before delegating. + try { + c = findClass(name); + if (resolve) resolveClass(c); + return c; + } catch (ClassNotFoundException ignored) { + // Fall back to parent. + } + + // Final fallback — parent (may throw ClassNotFoundException). + c = getParent().loadClass(name); + if (resolve) resolveClass(c); + return c; + } + } +} diff --git a/plugin/src/test/java/ru/abstractmenus/addon/AddonClassLoaderTest.java b/plugin/src/test/java/ru/abstractmenus/addon/AddonClassLoaderTest.java new file mode 100644 index 0000000..9acb59c --- /dev/null +++ b/plugin/src/test/java/ru/abstractmenus/addon/AddonClassLoaderTest.java @@ -0,0 +1,100 @@ +package ru.abstractmenus.addon; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import ru.abstractmenus.api.MenuExtension; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.net.URL; +import java.nio.file.Path; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; + +import static org.junit.jupiter.api.Assertions.*; + +class AddonClassLoaderTest { + + @Test + void parentFirstForApiPackage(@TempDir Path tmp) throws Exception { + // A jar that SHIPS its own copy of a class in ru.abstractmenus.api.*. + // The classloader must still prefer the parent's copy. + File jar = tmp.resolve("addon.jar").toFile(); + try (JarOutputStream out = new JarOutputStream(new FileOutputStream(jar))) { + // Use the real parent class's bytes (we just want the jar to contain + // the path). The key point is: the loader returns the PARENT's + // Class, not a freshly-defined one from the jar. + byte[] bytes = classBytes(MenuExtension.class); + out.putNextEntry(new JarEntry("ru/abstractmenus/api/MenuExtension.class")); + out.write(bytes); + out.closeEntry(); + } + + try (AddonClassLoader cl = new AddonClassLoader( + new URL[]{jar.toURI().toURL()}, + AddonClassLoaderTest.class.getClassLoader())) { + + Class loaded = cl.loadClass("ru.abstractmenus.api.MenuExtension"); + assertSame(MenuExtension.class, loaded, + "parent-first must return the parent's Class object"); + } + } + + @Test + void childFirstForEverythingElse(@TempDir Path tmp) throws Exception { + // SampleAddonClass is a static inner class; its binary name contains '$'. + String binaryName = SampleAddonClass.class.getName(); // ru.abstractmenus.addon.AddonClassLoaderTest$SampleAddonClass + String entryPath = binaryName.replace('.', '/') + ".class"; + + File jar = tmp.resolve("addon.jar").toFile(); + try (JarOutputStream out = new JarOutputStream(new FileOutputStream(jar))) { + byte[] bytes = classBytes(SampleAddonClass.class); + out.putNextEntry(new JarEntry(entryPath)); + out.write(bytes); + out.closeEntry(); + } + + try (AddonClassLoader cl = new AddonClassLoader( + new URL[]{jar.toURI().toURL()}, + AddonClassLoaderTest.class.getClassLoader())) { + + Class loaded = cl.loadClass(binaryName); + assertEquals(binaryName, loaded.getName()); + } + } + + @Test + void missingClass_throwsClassNotFound(@TempDir Path tmp) throws Exception { + File jar = tmp.resolve("empty.jar").toFile(); + try (JarOutputStream out = new JarOutputStream(new FileOutputStream(jar))) { + // empty jar + } + + try (AddonClassLoader cl = new AddonClassLoader( + new URL[]{jar.toURI().toURL()}, + AddonClassLoaderTest.class.getClassLoader())) { + + assertThrows(ClassNotFoundException.class, + () -> cl.loadClass("com.nowhere.Missing")); + } + } + + // --- helpers --- + + private static byte[] classBytes(Class cls) throws Exception { + String path = cls.getName().replace('.', '/') + ".class"; + try (InputStream in = cls.getClassLoader().getResourceAsStream(path); + ByteArrayOutputStream buf = new ByteArrayOutputStream()) { + if (in == null) throw new IllegalStateException("Cannot find " + path); + in.transferTo(buf); + return buf.toByteArray(); + } + } + + /** Public fixture class — a test jar will pack its bytecode. */ + public static class SampleAddonClass { + public String ping() { return "pong"; } + } +} From f0c7b50a640a96b840b36973da0776206d2a2297 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Thu, 23 Apr 2026 08:29:11 +0500 Subject: [PATCH 36/71] feat(addon): AddonStatus enum + LoadedAddon container Co-Authored-By: Claude Opus 4.7 (1M context) --- .../ru/abstractmenus/addon/AddonStatus.java | 13 +++++++ .../ru/abstractmenus/addon/LoadedAddon.java | 36 +++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 plugin/src/main/java/ru/abstractmenus/addon/AddonStatus.java create mode 100644 plugin/src/main/java/ru/abstractmenus/addon/LoadedAddon.java diff --git a/plugin/src/main/java/ru/abstractmenus/addon/AddonStatus.java b/plugin/src/main/java/ru/abstractmenus/addon/AddonStatus.java new file mode 100644 index 0000000..d49d661 --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/addon/AddonStatus.java @@ -0,0 +1,13 @@ +package ru.abstractmenus.addon; + +/** Lifecycle state of an addon as tracked by {@code AddonManager}. */ +public enum AddonStatus { + /** Addon discovered and parsed; not yet enabled. */ + PENDING, + /** Enabled — {@code onEnable} completed without throwing. */ + ENABLED, + /** Cleanly disabled — {@code onDisable} ran and registry entries were cleared. */ + DISABLED, + /** Failed to load or enable — see {@link LoadedAddon#error()} for the cause. */ + FAILED +} diff --git a/plugin/src/main/java/ru/abstractmenus/addon/LoadedAddon.java b/plugin/src/main/java/ru/abstractmenus/addon/LoadedAddon.java new file mode 100644 index 0000000..15920d8 --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/addon/LoadedAddon.java @@ -0,0 +1,36 @@ +package ru.abstractmenus.addon; + +import ru.abstractmenus.api.MenuExtension; + +/** + * Mutable container representing an AM-loaded addon. Holds the parsed + * {@link AddonConf}, the live {@link MenuExtension} instance, its + * {@link AddonClassLoader}, and lifecycle status. + * + *

Instances are created by {@code AddonManager} during discovery and + * transition through enable → disable / failed states. + */ +public final class LoadedAddon { + + private final AddonConf conf; + private final AddonClassLoader classLoader; + private MenuExtension extension; // null until onLoad completes + private AddonStatus status = AddonStatus.PENDING; + private Throwable error; // non-null iff status == FAILED + + public LoadedAddon(AddonConf conf, AddonClassLoader classLoader) { + this.conf = conf; + this.classLoader = classLoader; + } + + public AddonConf conf() { return conf; } + public AddonClassLoader classLoader() { return classLoader; } + public MenuExtension extension() { return extension; } + public AddonStatus status() { return status; } + public Throwable error() { return error; } + + public void setExtension(MenuExtension e) { this.extension = e; } + public void markEnabled() { this.status = AddonStatus.ENABLED; this.error = null; } + public void markDisabled() { this.status = AddonStatus.DISABLED; this.error = null; } + public void markFailed(Throwable t) { this.status = AddonStatus.FAILED; this.error = t; } +} From 2f6f4a31cc1e456b13e361a980b964bd6e1113c6 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Thu, 23 Apr 2026 08:30:28 +0500 Subject: [PATCH 37/71] feat(addon): AddonManager skeleton (no loading logic yet) Co-Authored-By: Claude Opus 4.7 (1M context) --- .../ru/abstractmenus/addon/AddonManager.java | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 plugin/src/main/java/ru/abstractmenus/addon/AddonManager.java diff --git a/plugin/src/main/java/ru/abstractmenus/addon/AddonManager.java b/plugin/src/main/java/ru/abstractmenus/addon/AddonManager.java new file mode 100644 index 0000000..9182de1 --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/addon/AddonManager.java @@ -0,0 +1,78 @@ +package ru.abstractmenus.addon; + +import ru.abstractmenus.AbstractMenus; +import ru.abstractmenus.api.AbstractMenusApi; +import ru.abstractmenus.api.Logger; + +import java.nio.file.Path; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Optional; + +/** + * Loads, enables, and manages AM-loaded addons (the lightweight jars in + * {@code plugins/AbstractMenus/addons/}). + * + *

Plugin-as-addons (Path 1) are not handled here — they boot through + * Bukkit's own plugin lifecycle and look up the API via + * {@link AbstractMenusApi#get()}. + */ +public final class AddonManager { + + private final AbstractMenus plugin; + private final Path addonsDir; + private final AbstractMenusApi api; + + /** name (lowercased) → LoadedAddon, insertion-ordered (matches enable order) */ + private final Map addons = new LinkedHashMap<>(); + + public AddonManager(AbstractMenus plugin, AbstractMenusApi api) { + this.plugin = plugin; + this.api = api; + this.addonsDir = plugin.getDataFolder().toPath().resolve("addons"); + } + + /** + * Discover, parse, sort, and enable every addon in the addons directory. + * Safe to call once during plugin enable. If the directory doesn't exist, + * creates it and returns without enabling anything. + */ + public void loadAll() { + // Impl — Tasks 6 + 7 + Logger.info("AddonManager.loadAll — not yet implemented"); + } + + /** + * Disable every loaded addon (in reverse enable order) and release + * classloader resources. Called from plugin onDisable. + */ + public void unloadAll() { + // Impl — Task 8 + } + + /** + * Reload a single AM-loaded addon by name: disable → close classloader → + * re-parse the jar → enable. Returns the new {@link LoadedAddon}, or + * empty if no addon of that name is currently loaded. + * + * @param name addon name (case-insensitive) + * @return the freshly loaded addon, or empty if not found / no jar present + */ + public Optional reload(String name) { + // Impl — Task 9 + return Optional.empty(); + } + + public Collection loaded() { + return Collections.unmodifiableCollection(addons.values()); + } + + public Optional get(String name) { + return Optional.ofNullable(addons.get(name.toLowerCase())); + } + + // package-private helpers filled in by subsequent tasks (discover, readAddonJar, + // instantiate, rollbackRegistrations, findJarByName) +} From 0d47c02c3709fa44a2eacbccaed53b1844511183 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Thu, 23 Apr 2026 08:31:49 +0500 Subject: [PATCH 38/71] =?UTF-8?q?feat(addon):=20AddonManager.discover=20?= =?UTF-8?q?=E2=80=94=20scan=20addons=20dir=20+=20parse=20addon.conf?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 (1M context) --- .../ru/abstractmenus/addon/AddonManager.java | 74 ++++++++++++++++++- 1 file changed, 72 insertions(+), 2 deletions(-) diff --git a/plugin/src/main/java/ru/abstractmenus/addon/AddonManager.java b/plugin/src/main/java/ru/abstractmenus/addon/AddonManager.java index 9182de1..969ab88 100644 --- a/plugin/src/main/java/ru/abstractmenus/addon/AddonManager.java +++ b/plugin/src/main/java/ru/abstractmenus/addon/AddonManager.java @@ -73,6 +73,76 @@ public Optional get(String name) { return Optional.ofNullable(addons.get(name.toLowerCase())); } - // package-private helpers filled in by subsequent tasks (discover, readAddonJar, - // instantiate, rollbackRegistrations, findJarByName) + /** + * Scan {@link #addonsDir} for {@code *.jar} files. For each, extract + * {@code addon.conf}, parse it, and build a LoadedAddon (without enabling + * — status stays PENDING). + * + *

Jars that are missing addon.conf, have malformed addon.conf, or + * duplicate a name already seen are logged and skipped — not fatal. + * + * @return map of name (lowercased) → PENDING LoadedAddon, in discovery + * order (stable for the later topological sort) + */ + Map discover() { + Map pending = new LinkedHashMap<>(); + + if (!java.nio.file.Files.isDirectory(addonsDir)) { + try { + java.nio.file.Files.createDirectories(addonsDir); + } catch (java.io.IOException e) { + Logger.warning("Could not create addons directory " + addonsDir + ": " + e.getMessage()); + } + return pending; + } + + try (var stream = java.nio.file.Files.newDirectoryStream(addonsDir, "*.jar")) { + for (Path jar : stream) { + try { + LoadedAddon addon = readAddonJar(jar); + String key = addon.conf().name().toLowerCase(); + if (pending.containsKey(key)) { + Logger.warning("Duplicate addon name '" + addon.conf().name() + + "' — ignoring " + jar.getFileName()); + try { addon.classLoader().close(); } catch (Exception ignored) {} + continue; + } + pending.put(key, addon); + } catch (Exception e) { + Logger.warning("Failed to load addon " + jar.getFileName() + ": " + e.getMessage()); + } + } + } catch (java.io.IOException e) { + Logger.warning("Failed to scan addons directory: " + e.getMessage()); + } + + return pending; + } + + /** + * Read a single addon jar: extract {@code addon.conf}, parse it, build a + * classloader. Throws if addon.conf is missing or malformed. + */ + private LoadedAddon readAddonJar(Path jarPath) throws java.io.IOException { + String hocon; + try (var jar = new java.util.jar.JarFile(jarPath.toFile())) { + var entry = jar.getJarEntry("addon.conf"); + if (entry == null) { + throw new java.io.IOException("no addon.conf at jar root"); + } + try (var in = jar.getInputStream(entry)) { + hocon = new String(in.readAllBytes(), java.nio.charset.StandardCharsets.UTF_8); + } + } + + AddonConf conf = AddonConf.parse(hocon); + AddonClassLoader cl = new AddonClassLoader( + new java.net.URL[]{jarPath.toUri().toURL()}, + plugin.getClass().getClassLoader()); + + return new LoadedAddon(conf, cl); + } + + // package-private helpers filled in by subsequent tasks (instantiate, + // rollbackRegistrations, findJarByName) } From feca7e3cc7f7437795366460db6987e4f2e597d3 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Thu, 23 Apr 2026 08:33:58 +0500 Subject: [PATCH 39/71] =?UTF-8?q?feat(addon):=20AddonManager.loadAll=20?= =?UTF-8?q?=E2=80=94=20sort=20+=20pluginDep=20filter=20+=20onLoad/onEnable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 (1M context) --- .../ru/abstractmenus/addon/AddonManager.java | 113 +++++++++++++++++- 1 file changed, 109 insertions(+), 4 deletions(-) diff --git a/plugin/src/main/java/ru/abstractmenus/addon/AddonManager.java b/plugin/src/main/java/ru/abstractmenus/addon/AddonManager.java index 969ab88..d1f2686 100644 --- a/plugin/src/main/java/ru/abstractmenus/addon/AddonManager.java +++ b/plugin/src/main/java/ru/abstractmenus/addon/AddonManager.java @@ -8,6 +8,7 @@ import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Optional; @@ -40,8 +41,114 @@ public AddonManager(AbstractMenus plugin, AbstractMenusApi api) { * creates it and returns without enabling anything. */ public void loadAll() { - // Impl — Tasks 6 + 7 - Logger.info("AddonManager.loadAll — not yet implemented"); + Map pending = discover(); + if (pending.isEmpty()) { + Logger.info("No AM-loaded addons found in " + addonsDir); + return; + } + + // Soft-filter: drop addons whose required Bukkit plugin deps are missing. + var pluginManager = plugin.getServer().getPluginManager(); + var byName = new LinkedHashMap(); + for (LoadedAddon la : pending.values()) { + AddonConf c = la.conf(); + boolean missing = false; + for (String dep : c.pluginDependencies()) { + if (pluginManager.getPlugin(dep) == null) { + Logger.warning("Addon " + c.name() + + " requires plugin '" + dep + "' which is not installed — skipping"); + la.markFailed(new IllegalStateException("missing plugin dependency: " + dep)); + missing = true; + break; + } + } + if (missing) { + addons.put(c.name().toLowerCase(), la); // keep the failed entry visible in /am addons list + continue; + } + byName.put(c.name().toLowerCase(), la); + } + + if (byName.isEmpty()) return; + + // Sort by addon-level dependencies. + Map> depGraph = new LinkedHashMap<>(); + for (var e : byName.entrySet()) { + List deps = e.getValue().conf().addonDependencies().stream() + .map(String::toLowerCase).toList(); + depGraph.put(e.getKey(), deps); + } + List order; + try { + order = AddonDependencyGraph.topoSort(depGraph); + } catch (AddonDependencyException ex) { + Logger.severe("Addon dependency graph error: " + ex.getMessage()); + for (var la : byName.values()) { + la.markFailed(ex); + addons.put(la.conf().name().toLowerCase(), la); + } + return; + } + + // Stage 1: onLoad for all — ordering-independent setup. + for (String k : order) { + LoadedAddon la = byName.get(k); + try { + la.setExtension(instantiate(la)); + la.extension().onLoad(api); + } catch (Throwable t) { + Logger.severe("Addon " + la.conf().name() + " failed in onLoad: " + t); + t.printStackTrace(); + la.markFailed(t); + } + } + + // Stage 2: onEnable in dependency order. + for (String k : order) { + LoadedAddon la = byName.get(k); + if (la.status() == AddonStatus.FAILED) { + addons.put(k, la); + continue; + } + try { + la.extension().onEnable(api); + la.markEnabled(); + Logger.info("Enabled addon: " + la.conf().name() + + " v" + la.conf().version() + + (la.conf().targetApiVersion() == null + ? "" + : " (built against API " + la.conf().targetApiVersion() + ")")); + } catch (Throwable t) { + Logger.severe("Addon " + la.conf().name() + " failed in onEnable: " + t); + t.printStackTrace(); + la.markFailed(t); + rollbackRegistrations(la); + } + addons.put(k, la); + } + } + + /** + * Reflectively instantiate the addon's main class and verify it implements + * MenuExtension. + */ + private ru.abstractmenus.api.MenuExtension instantiate(LoadedAddon la) throws Exception { + Class main = la.classLoader().loadClass(la.conf().main()); + if (!ru.abstractmenus.api.MenuExtension.class.isAssignableFrom(main)) { + throw new IllegalStateException("main class " + main.getName() + + " does not implement MenuExtension"); + } + return (ru.abstractmenus.api.MenuExtension) main.getDeclaredConstructor().newInstance(); + } + + /** Strip any type registrations the failed addon managed to make. */ + private void rollbackRegistrations(LoadedAddon la) { + if (la.extension() == null) return; + api.actions().unregisterAll(la.extension()); + api.rules().unregisterAll(la.extension()); + api.activators().unregisterAll(la.extension()); + api.itemProperties().unregisterAll(la.extension()); + api.catalogs().unregisterAll(la.extension()); } /** @@ -143,6 +250,4 @@ private LoadedAddon readAddonJar(Path jarPath) throws java.io.IOException { return new LoadedAddon(conf, cl); } - // package-private helpers filled in by subsequent tasks (instantiate, - // rollbackRegistrations, findJarByName) } From 93f0bdf288a2771815622594dd5f504e5cff4079 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Thu, 23 Apr 2026 08:35:13 +0500 Subject: [PATCH 40/71] =?UTF-8?q?feat(addon):=20AddonManager.unloadAll=20?= =?UTF-8?q?=E2=80=94=20onDisable=20+=20classloader=20close?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 (1M context) --- .../ru/abstractmenus/addon/AddonManager.java | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/plugin/src/main/java/ru/abstractmenus/addon/AddonManager.java b/plugin/src/main/java/ru/abstractmenus/addon/AddonManager.java index d1f2686..7663ce0 100644 --- a/plugin/src/main/java/ru/abstractmenus/addon/AddonManager.java +++ b/plugin/src/main/java/ru/abstractmenus/addon/AddonManager.java @@ -156,7 +156,28 @@ private void rollbackRegistrations(LoadedAddon la) { * classloader resources. Called from plugin onDisable. */ public void unloadAll() { - // Impl — Task 8 + // Disable in reverse enable order. + var reversed = new java.util.ArrayList<>(addons.values()); + java.util.Collections.reverse(reversed); + for (LoadedAddon la : reversed) { + try { + if (la.status() == AddonStatus.ENABLED && la.extension() != null) { + la.extension().onDisable(api); + } + rollbackRegistrations(la); + la.markDisabled(); + } catch (Throwable t) { + Logger.severe("Addon " + la.conf().name() + " failed in onDisable: " + t); + t.printStackTrace(); + // Don't let one bad disable block the others. + } + try { + la.classLoader().close(); + } catch (Exception e) { + Logger.warning("Addon " + la.conf().name() + " classloader close failed: " + e); + } + } + addons.clear(); } /** From a7e4022de96857f8059ec3474b31c1c5456521a4 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Thu, 23 Apr 2026 08:36:36 +0500 Subject: [PATCH 41/71] =?UTF-8?q?feat(addon):=20AddonManager.reload=20?= =?UTF-8?q?=E2=80=94=20single-addon=20hot=20reload?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 (1M context) --- .../ru/abstractmenus/addon/AddonManager.java | 71 ++++++++++++++++++- 1 file changed, 69 insertions(+), 2 deletions(-) diff --git a/plugin/src/main/java/ru/abstractmenus/addon/AddonManager.java b/plugin/src/main/java/ru/abstractmenus/addon/AddonManager.java index 7663ce0..d2520ff 100644 --- a/plugin/src/main/java/ru/abstractmenus/addon/AddonManager.java +++ b/plugin/src/main/java/ru/abstractmenus/addon/AddonManager.java @@ -189,8 +189,75 @@ public void unloadAll() { * @return the freshly loaded addon, or empty if not found / no jar present */ public Optional reload(String name) { - // Impl — Task 9 - return Optional.empty(); + String key = name.toLowerCase(); + LoadedAddon existing = addons.get(key); + if (existing == null) return Optional.empty(); + + // Disable + unhook current instance. + try { + if (existing.status() == AddonStatus.ENABLED && existing.extension() != null) { + existing.extension().onDisable(api); + } + rollbackRegistrations(existing); + } catch (Throwable t) { + Logger.warning("Addon " + existing.conf().name() + + " failed in onDisable during reload: " + t); + } + try { existing.classLoader().close(); } catch (Exception ignored) {} + addons.remove(key); + + // Re-discover: find the jar whose addon.conf name matches. + Path freshJar = findJarByName(name); + if (freshJar == null) { + Logger.warning("Addon " + name + " jar no longer present — not reloaded"); + return Optional.empty(); + } + + LoadedAddon fresh; + try { + fresh = readAddonJar(freshJar); + } catch (Exception e) { + Logger.severe("Addon " + name + " jar failed to re-parse: " + e.getMessage()); + return Optional.empty(); + } + + // Enable the single addon. We don't re-chain the full topological sort + // for a single-addon reload — assume its addonDependencies are already + // enabled (they were, before this reload). + try { + fresh.setExtension(instantiate(fresh)); + fresh.extension().onLoad(api); + fresh.extension().onEnable(api); + fresh.markEnabled(); + addons.put(fresh.conf().name().toLowerCase(), fresh); + Logger.info("Reloaded addon: " + fresh.conf().name() + " v" + fresh.conf().version()); + } catch (Throwable t) { + Logger.severe("Addon " + name + " failed during reload: " + t); + t.printStackTrace(); + fresh.markFailed(t); + rollbackRegistrations(fresh); + addons.put(fresh.conf().name().toLowerCase(), fresh); + } + + return Optional.of(fresh); + } + + /** Scan addonsDir again, return the first jar whose addon.conf.name matches. */ + private Path findJarByName(String name) { + if (!java.nio.file.Files.isDirectory(addonsDir)) return null; + try (var stream = java.nio.file.Files.newDirectoryStream(addonsDir, "*.jar")) { + for (Path jar : stream) { + try (var jf = new java.util.jar.JarFile(jar.toFile())) { + var entry = jf.getJarEntry("addon.conf"); + if (entry == null) continue; + String hocon = new String(jf.getInputStream(entry).readAllBytes(), + java.nio.charset.StandardCharsets.UTF_8); + AddonConf c = AddonConf.parse(hocon); + if (c.name().equalsIgnoreCase(name)) return jar; + } catch (Exception ignored) {} + } + } catch (Exception ignored) {} + return null; } public Collection loaded() { From aac110c1dd7bfeceae5bc5ca5dd645ead3cb5455 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Thu, 23 Apr 2026 08:39:11 +0500 Subject: [PATCH 42/71] feat(plugin): wire AddonManager into onEnable + onDisable loadAll runs after CoreExtension completes so addon-registered types are available before menu parsing. unloadAll fires on plugin disable, reverse enable order. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/main/java/ru/abstractmenus/AbstractMenus.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java b/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java index bc9652f..201a490 100644 --- a/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java +++ b/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java @@ -29,6 +29,7 @@ import ru.abstractmenus.commands.am.CommandServe; import ru.abstractmenus.commands.var.*; import ru.abstractmenus.commands.varp.*; +import ru.abstractmenus.addon.AddonManager; import ru.abstractmenus.api.MenuExtension; import ru.abstractmenus.core.CoreExtension; import ru.abstractmenus.handlers.*; @@ -69,6 +70,7 @@ public final class AbstractMenus extends JavaPlugin { private Metrics metrics; private FoliaLib foliaLib; private AbstractMenusApi api; + private AddonManager addonManager; @Getter @Setter @@ -80,6 +82,8 @@ public Plugin getPlugin() { public AbstractMenusApi getApi() { return api; } + public AddonManager getAddonManager() { return addonManager; } + public VariableManager getVariableManager() { return VariableManagerImpl.instance(); } @@ -149,6 +153,11 @@ public void onEnable() { core.onLoad(api); core.onEnable(api); + // External addons (plugins/AbstractMenus/addons/*.jar) load here so + // their registered types are available before menus parse. + this.addonManager = new AddonManager(this, api); + addonManager.loadAll(); + loadMenus(); getServer().getPluginManager().registerEvents(new InventoryListener(), this); @@ -186,6 +195,8 @@ public void openMenu(Activator activator, Object ctx, Player player, Menu menu) @Override public void onDisable() { + if (addonManager != null) addonManager.unloadAll(); + if (MenuManager.instance() != null) { MenuManager.instance().unloadAll(); } From b675c498b23b2f17db48201debdd3ee574642188 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Thu, 23 Apr 2026 08:41:47 +0500 Subject: [PATCH 43/71] feat(plugin): /am addons list|reload|info command Co-Authored-By: Claude Opus 4.7 (1M context) --- .../java/ru/abstractmenus/AbstractMenus.java | 4 +- .../commands/AbstractMenuCommand.java | 3 +- .../commands/am/CommandAddons.java | 118 ++++++++++++++++++ 3 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 plugin/src/main/java/ru/abstractmenus/commands/am/CommandAddons.java diff --git a/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java b/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java index 201a490..0d1a72d 100644 --- a/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java +++ b/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java @@ -23,6 +23,7 @@ import ru.abstractmenus.commands.Command; import ru.abstractmenus.commands.VarCommand; import ru.abstractmenus.commands.VarpCommand; +import ru.abstractmenus.commands.am.CommandAddons; import ru.abstractmenus.commands.am.CommandOpen; import ru.abstractmenus.commands.am.CommandPluginVersion; import ru.abstractmenus.commands.am.CommandReload; @@ -217,7 +218,8 @@ private void registerCommands(MainConfig config) { .addSub("reload", new CommandReload()) .addSub("open", new CommandOpen(config)) .addSub("serve", new CommandServe()) - .addSub("version", new CommandPluginVersion()); + .addSub("version", new CommandPluginVersion()) + .addSub("addons", new CommandAddons()); Command var = new VarCommand("am.admin") .addSub("get", new VarGet()) diff --git a/plugin/src/main/java/ru/abstractmenus/commands/AbstractMenuCommand.java b/plugin/src/main/java/ru/abstractmenus/commands/AbstractMenuCommand.java index 1b136bd..d8dd46e 100644 --- a/plugin/src/main/java/ru/abstractmenus/commands/AbstractMenuCommand.java +++ b/plugin/src/main/java/ru/abstractmenus/commands/AbstractMenuCommand.java @@ -13,7 +13,8 @@ public AbstractMenuCommand(String permission) { Colors.of("&7/am open &e- Open menu with specific name for player"), Colors.of("&7/am reload &e- Reload all menu files. Also close all opened menus for player"), Colors.of("&7/am serve &e- Turn on/off automatic menu reloading if file changed"), - Colors.of("&7/am version &e- show plugin version") + Colors.of("&7/am version &e- show plugin version"), + Colors.of("&7/am addons list|reload|info &e- manage AM-loaded addons") ); } diff --git a/plugin/src/main/java/ru/abstractmenus/commands/am/CommandAddons.java b/plugin/src/main/java/ru/abstractmenus/commands/am/CommandAddons.java new file mode 100644 index 0000000..518c480 --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/commands/am/CommandAddons.java @@ -0,0 +1,118 @@ +package ru.abstractmenus.commands.am; + +import org.bukkit.command.CommandSender; +import ru.abstractmenus.AbstractMenus; +import ru.abstractmenus.addon.AddonManager; +import ru.abstractmenus.addon.AddonStatus; +import ru.abstractmenus.addon.LoadedAddon; +import ru.abstractmenus.api.text.Colors; +import ru.abstractmenus.commands.Command; + +/** {@code /am addons [list|reload |info ]} */ +public class CommandAddons extends Command { + + public CommandAddons() { + setUsage( + Colors.of("&7/am addons list &e- list all AM-loaded addons"), + Colors.of("&7/am addons reload &e- reload a single addon"), + Colors.of("&7/am addons info &e- show addon metadata") + ); + } + + @Override + public void execute(CommandSender sender, String[] args) { + AddonManager am = AbstractMenus.instance().getAddonManager(); + if (am == null) { + sender.sendMessage(Colors.of("&cAddonManager not yet initialised.")); + return; + } + + if (args.length == 0) { + sender.sendMessage(getUsage()); + return; + } + + switch (args[0].toLowerCase()) { + case "list" -> list(sender, am); + case "reload" -> reload(sender, am, args); + case "info" -> info(sender, am, args); + default -> sender.sendMessage(getUsage()); + } + } + + private void list(CommandSender sender, AddonManager am) { + var addons = am.loaded(); + if (addons.isEmpty()) { + sender.sendMessage(Colors.of("&7No AM-loaded addons.")); + return; + } + sender.sendMessage(Colors.of("&e&lAddons (" + addons.size() + "):")); + for (LoadedAddon la : addons) { + String color = switch (la.status()) { + case ENABLED -> "&a"; + case DISABLED -> "&7"; + case FAILED -> "&c"; + case PENDING -> "&e"; + }; + sender.sendMessage(Colors.of(color + " " + la.conf().name() + + " &8v" + la.conf().version() + + " &7[" + la.status() + "]")); + } + } + + private void reload(CommandSender sender, AddonManager am, String[] args) { + if (args.length < 2) { + sender.sendMessage(Colors.of("&cUsage: /am addons reload ")); + return; + } + String name = args[1]; + var result = am.reload(name); + if (result.isEmpty()) { + sender.sendMessage(Colors.of("&cAddon '" + name + "' not found or no jar present.")); + return; + } + LoadedAddon la = result.get(); + if (la.status() == AddonStatus.ENABLED) { + sender.sendMessage(Colors.of("&aReloaded " + la.conf().name() + ".")); + } else { + sender.sendMessage(Colors.of("&cReload failed: " + + (la.error() == null ? "unknown error" : la.error().getMessage()))); + } + } + + private void info(CommandSender sender, AddonManager am, String[] args) { + if (args.length < 2) { + sender.sendMessage(Colors.of("&cUsage: /am addons info ")); + return; + } + var opt = am.get(args[1]); + if (opt.isEmpty()) { + sender.sendMessage(Colors.of("&cAddon '" + args[1] + "' not found.")); + return; + } + LoadedAddon la = opt.get(); + var c = la.conf(); + sender.sendMessage(Colors.of("&e&l" + c.name() + " &7v" + c.version())); + sender.sendMessage(Colors.of("&7 status: &f" + la.status())); + if (!c.authors().isEmpty()) { + sender.sendMessage(Colors.of("&7 authors: &f" + String.join(", ", c.authors()))); + } + if (!c.description().isEmpty()) { + sender.sendMessage(Colors.of("&7 description: &f" + c.description())); + } + if (c.targetApiVersion() != null) { + sender.sendMessage(Colors.of("&7 targetApiVersion: &f" + c.targetApiVersion())); + } + if (!c.addonDependencies().isEmpty()) { + sender.sendMessage(Colors.of("&7 addonDependencies: &f" + + String.join(", ", c.addonDependencies()))); + } + if (!c.pluginDependencies().isEmpty()) { + sender.sendMessage(Colors.of("&7 pluginDependencies: &f" + + String.join(", ", c.pluginDependencies()))); + } + if (la.status() == AddonStatus.FAILED && la.error() != null) { + sender.sendMessage(Colors.of("&7 error: &c" + la.error().getMessage())); + } + } +} From 7fb11a0090356af63791122f1644bf206cefc08d Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Thu, 23 Apr 2026 08:49:27 +0500 Subject: [PATCH 44/71] =?UTF-8?q?test(addon):=20AddonManagerIntegrationTes?= =?UTF-8?q?t=20=E2=80=94=20end-to-end=20jar=20load=20+=20registration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Builds a test-addon.jar at test-time via JarOutputStream, feeds it to AddonManager via a test-only package-private constructor, and asserts the addon's action registers and unloadAll cleans it up. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../ru/abstractmenus/addon/AddonManager.java | 55 ++++-- .../addon/AddonManagerIntegrationTest.java | 171 ++++++++++++++++++ 2 files changed, 209 insertions(+), 17 deletions(-) create mode 100644 plugin/src/test/java/ru/abstractmenus/addon/AddonManagerIntegrationTest.java diff --git a/plugin/src/main/java/ru/abstractmenus/addon/AddonManager.java b/plugin/src/main/java/ru/abstractmenus/addon/AddonManager.java index d2520ff..1285cc9 100644 --- a/plugin/src/main/java/ru/abstractmenus/addon/AddonManager.java +++ b/plugin/src/main/java/ru/abstractmenus/addon/AddonManager.java @@ -35,6 +35,18 @@ public AddonManager(AbstractMenus plugin, AbstractMenusApi api) { this.addonsDir = plugin.getDataFolder().toPath().resolve("addons"); } + /** + * Test-only overload: inject addonsDir directly, skip Bukkit plugin-dep + * checks (since no {@code plugin.getServer()} is available in pure-unit + * tests). Any addon with a non-empty {@code pluginDependencies} will fail + * under this constructor. + */ + AddonManager(java.nio.file.Path addonsDir, ru.abstractmenus.api.AbstractMenusApi api) { + this.plugin = null; + this.api = api; + this.addonsDir = addonsDir; + } + /** * Discover, parse, sort, and enable every addon in the addons directory. * Safe to call once during plugin enable. If the directory doesn't exist, @@ -48,25 +60,31 @@ public void loadAll() { } // Soft-filter: drop addons whose required Bukkit plugin deps are missing. - var pluginManager = plugin.getServer().getPluginManager(); + // In test mode (plugin == null), skip this check — tests must not declare + // pluginDependencies. var byName = new LinkedHashMap(); - for (LoadedAddon la : pending.values()) { - AddonConf c = la.conf(); - boolean missing = false; - for (String dep : c.pluginDependencies()) { - if (pluginManager.getPlugin(dep) == null) { - Logger.warning("Addon " + c.name() - + " requires plugin '" + dep + "' which is not installed — skipping"); - la.markFailed(new IllegalStateException("missing plugin dependency: " + dep)); - missing = true; - break; + if (plugin != null) { + var pluginManager = plugin.getServer().getPluginManager(); + for (LoadedAddon la : pending.values()) { + AddonConf c = la.conf(); + boolean missing = false; + for (String dep : c.pluginDependencies()) { + if (pluginManager.getPlugin(dep) == null) { + Logger.warning("Addon " + c.name() + + " requires plugin '" + dep + "' which is not installed — skipping"); + la.markFailed(new IllegalStateException("missing plugin dependency: " + dep)); + missing = true; + break; + } } + if (missing) { + addons.put(c.name().toLowerCase(), la); // keep the failed entry visible in /am addons list + continue; + } + byName.put(c.name().toLowerCase(), la); } - if (missing) { - addons.put(c.name().toLowerCase(), la); // keep the failed entry visible in /am addons list - continue; - } - byName.put(c.name().toLowerCase(), la); + } else { + byName.putAll(pending); } if (byName.isEmpty()) return; @@ -331,9 +349,12 @@ private LoadedAddon readAddonJar(Path jarPath) throws java.io.IOException { } AddonConf conf = AddonConf.parse(hocon); + ClassLoader parent = (plugin != null) + ? plugin.getClass().getClassLoader() + : AddonManager.class.getClassLoader(); AddonClassLoader cl = new AddonClassLoader( new java.net.URL[]{jarPath.toUri().toURL()}, - plugin.getClass().getClassLoader()); + parent); return new LoadedAddon(conf, cl); } diff --git a/plugin/src/test/java/ru/abstractmenus/addon/AddonManagerIntegrationTest.java b/plugin/src/test/java/ru/abstractmenus/addon/AddonManagerIntegrationTest.java new file mode 100644 index 0000000..c2ad741 --- /dev/null +++ b/plugin/src/test/java/ru/abstractmenus/addon/AddonManagerIntegrationTest.java @@ -0,0 +1,171 @@ +package ru.abstractmenus.addon; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import ru.abstractmenus.api.AbstractMenusApi; +import ru.abstractmenus.api.Action; +import ru.abstractmenus.api.Activator; +import ru.abstractmenus.api.Catalog; +import ru.abstractmenus.api.MenuExtension; +import ru.abstractmenus.api.Rule; +import ru.abstractmenus.api.TypeRegistry; +import ru.abstractmenus.api.TypeRegistryImpl; +import ru.abstractmenus.api.inventory.ItemProperty; +import ru.abstractmenus.api.inventory.Menu; +import ru.abstractmenus.api.variables.VariableManager; +import ru.abstractmenus.hocon.api.ConfigNode; +import ru.abstractmenus.hocon.api.serialize.NodeSerializeException; +import ru.abstractmenus.hocon.api.serialize.NodeSerializer; +import ru.abstractmenus.hocon.api.serialize.NodeSerializers; + +import java.io.ByteArrayOutputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * End-to-end integration test for {@link AddonManager}: builds a real addon + * jar at test-time via {@link JarOutputStream}, feeds it through + * {@code loadAll()}, verifies the addon's action is registered, then verifies + * {@code unloadAll()} strips the registration. + * + *

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

getOpenedMenu(org.bukkit.entity.Player player) { return Optional.empty(); } + @Override public String apiVersion() { return "test"; } + } +} From 4b7704af4927ecba638054e05b7d1a45dd23ecf8 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Thu, 23 Apr 2026 09:10:22 +0500 Subject: [PATCH 45/71] =?UTF-8?q?feat(api):=20add=20ProviderRegistry=20?= =?UTF-8?q?=E2=80=94=20pluggable=20replacement=20for=20Handlers=20statics?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 (1M context) --- .../abstractmenus/api/ProviderRegistry.java | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 api/src/main/java/ru/abstractmenus/api/ProviderRegistry.java diff --git a/api/src/main/java/ru/abstractmenus/api/ProviderRegistry.java b/api/src/main/java/ru/abstractmenus/api/ProviderRegistry.java new file mode 100644 index 0000000..73d6df8 --- /dev/null +++ b/api/src/main/java/ru/abstractmenus/api/ProviderRegistry.java @@ -0,0 +1,98 @@ +package ru.abstractmenus.api; + +import ru.abstractmenus.api.handler.EconomyHandler; +import ru.abstractmenus.api.handler.LevelHandler; +import ru.abstractmenus.api.handler.PermissionsHandler; +import ru.abstractmenus.api.handler.PlaceholderHandler; +import ru.abstractmenus.api.handler.SkinHandler; + +import java.util.Collection; + +/** + * Registry of pluggable handler providers (economy, permissions, levels, + * placeholders, skins). Replaces the old static {@code Handlers.set*()/get*()} + * facade with an owner-aware registry that supports multiple providers per + * section plus priority-based auto-resolution. + * + *

Registration

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

Resolution

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

Same shape for permissions / levels / placeholders / skins. + * + * @see AbstractMenusApi#providers() + */ +public interface ProviderRegistry { + + // ---- Economy --------------------------------------------------------- + + void registerEconomy(String id, EconomyHandler handler, int priority, MenuExtension owner); + EconomyHandler economy(); + EconomyHandler economy(String id); + Collection allEconomy(); + boolean hasEconomy(String id); + + // ---- Permissions ----------------------------------------------------- + + void registerPermissions(String id, PermissionsHandler handler, int priority, MenuExtension owner); + PermissionsHandler permissions(); + PermissionsHandler permissions(String id); + Collection allPermissions(); + boolean hasPermissions(String id); + + // ---- Levels ---------------------------------------------------------- + + void registerLevels(String id, LevelHandler handler, int priority, MenuExtension owner); + LevelHandler levels(); + LevelHandler levels(String id); + Collection allLevels(); + boolean hasLevels(String id); + + // ---- Placeholders ---------------------------------------------------- + + void registerPlaceholders(String id, PlaceholderHandler handler, int priority, MenuExtension owner); + PlaceholderHandler placeholders(); + PlaceholderHandler placeholders(String id); + Collection allPlaceholders(); + boolean hasPlaceholders(String id); + + // ---- Skins ----------------------------------------------------------- + + void registerSkins(String id, SkinHandler handler, int priority, MenuExtension owner); + SkinHandler skins(); + SkinHandler skins(String id); + Collection allSkins(); + boolean hasSkins(String id); + + // ---- Cleanup --------------------------------------------------------- + + /** + * Remove every provider registration (across all sections) owned by + * {@code owner}. Called by AddonManager when an addon is disabled. + * + * @param owner the extension whose providers should be cleared + */ + void unregisterAll(MenuExtension owner); +} From 4fb42a4a790233d68ce0498e36333d87c31d3985 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Thu, 23 Apr 2026 09:11:44 +0500 Subject: [PATCH 46/71] feat(api): expose ProviderRegistry via AbstractMenusApi.providers() Intermediate broken build state: plugin's AbstractMenusApiImpl doesn't implement providers() yet. Fixed in the next task. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../main/java/ru/abstractmenus/api/AbstractMenusApi.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/api/src/main/java/ru/abstractmenus/api/AbstractMenusApi.java b/api/src/main/java/ru/abstractmenus/api/AbstractMenusApi.java index 39f2e63..611909c 100644 --- a/api/src/main/java/ru/abstractmenus/api/AbstractMenusApi.java +++ b/api/src/main/java/ru/abstractmenus/api/AbstractMenusApi.java @@ -60,6 +60,14 @@ static AbstractMenusApi get() { /** @return the catalog registry */ TypeRegistry> catalogs(); + /** + * Handler-provider registry (economy, permissions, levels, placeholders, + * skins). Replaces the old static {@code Handlers} facade. + * + * @return the provider registry + */ + ProviderRegistry providers(); + /** * Shared HOCON serializer collection. Type registrations add to this * automatically; consumers rarely need to access it directly. From a50d3dfe222786fd79f20159d2cd54cd679a18f2 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Thu, 23 Apr 2026 09:13:53 +0500 Subject: [PATCH 47/71] feat(plugin): ProviderRegistryImpl with 5-section generic storage (8 tests) Tests will run once AbstractMenusApiImpl implements providers() in the next task. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../api/ProviderRegistryImpl.java | 134 ++++++++++++++++++ .../api/ProviderRegistryImplTest.java | 113 +++++++++++++++ 2 files changed, 247 insertions(+) create mode 100644 plugin/src/main/java/ru/abstractmenus/api/ProviderRegistryImpl.java create mode 100644 plugin/src/test/java/ru/abstractmenus/api/ProviderRegistryImplTest.java diff --git a/plugin/src/main/java/ru/abstractmenus/api/ProviderRegistryImpl.java b/plugin/src/main/java/ru/abstractmenus/api/ProviderRegistryImpl.java new file mode 100644 index 0000000..77a4ad8 --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/api/ProviderRegistryImpl.java @@ -0,0 +1,134 @@ +package ru.abstractmenus.api; + +import ru.abstractmenus.api.handler.EconomyHandler; +import ru.abstractmenus.api.handler.LevelHandler; +import ru.abstractmenus.api.handler.PermissionsHandler; +import ru.abstractmenus.api.handler.PlaceholderHandler; +import ru.abstractmenus.api.handler.SkinHandler; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Default {@link ProviderRegistry} implementation. Five sections sharing an + * inner generic {@link Section} class. Insertion-ordered per section so that + * equal-priority ties resolve to the first-registered entry. + * + *

Thread-safe for registration/unregistration via per-section synchronized + * methods, but production use expects all mutation to happen on the main + * server thread during plugin / extension enable/disable. + */ +public final class ProviderRegistryImpl implements ProviderRegistry { + + private final Section economy = new Section<>(); + private final Section permissions = new Section<>(); + private final Section levels = new Section<>(); + private final Section placeholders = new Section<>(); + private final Section skins = new Section<>(); + + // ---- Economy --------------------------------------------------------- + + @Override public void registerEconomy(String id, EconomyHandler h, int pr, MenuExtension o) { economy.put(id, h, pr, o); } + @Override public EconomyHandler economy() { return economy.auto(); } + @Override public EconomyHandler economy(String id) { return economy.byId(id); } + @Override public Collection allEconomy() { return economy.all(); } + @Override public boolean hasEconomy(String id) { return economy.has(id); } + + // ---- Permissions ----------------------------------------------------- + + @Override public void registerPermissions(String id, PermissionsHandler h, int pr, MenuExtension o) { permissions.put(id, h, pr, o); } + @Override public PermissionsHandler permissions() { return permissions.auto(); } + @Override public PermissionsHandler permissions(String id) { return permissions.byId(id); } + @Override public Collection allPermissions() { return permissions.all(); } + @Override public boolean hasPermissions(String id) { return permissions.has(id); } + + // ---- Levels ---------------------------------------------------------- + + @Override public void registerLevels(String id, LevelHandler h, int pr, MenuExtension o) { levels.put(id, h, pr, o); } + @Override public LevelHandler levels() { return levels.auto(); } + @Override public LevelHandler levels(String id) { return levels.byId(id); } + @Override public Collection allLevels() { return levels.all(); } + @Override public boolean hasLevels(String id) { return levels.has(id); } + + // ---- Placeholders ---------------------------------------------------- + + @Override public void registerPlaceholders(String id, PlaceholderHandler h, int pr, MenuExtension o) { placeholders.put(id, h, pr, o); } + @Override public PlaceholderHandler placeholders() { return placeholders.auto(); } + @Override public PlaceholderHandler placeholders(String id) { return placeholders.byId(id); } + @Override public Collection allPlaceholders() { return placeholders.all(); } + @Override public boolean hasPlaceholders(String id) { return placeholders.has(id); } + + // ---- Skins ----------------------------------------------------------- + + @Override public void registerSkins(String id, SkinHandler h, int pr, MenuExtension o) { skins.put(id, h, pr, o); } + @Override public SkinHandler skins() { return skins.auto(); } + @Override public SkinHandler skins(String id) { return skins.byId(id); } + @Override public Collection allSkins() { return skins.all(); } + @Override public boolean hasSkins(String id) { return skins.has(id); } + + // ---- Cleanup --------------------------------------------------------- + + @Override + public void unregisterAll(MenuExtension owner) { + economy.unregisterAll(owner); + permissions.unregisterAll(owner); + levels.unregisterAll(owner); + placeholders.unregisterAll(owner); + skins.unregisterAll(owner); + } + + // ---- Inner section --------------------------------------------------- + + private static final class Section { + private final Map> byId = new LinkedHashMap<>(); + private final Map> keysByOwner = new IdentityHashMap<>(); + + synchronized void put(String id, T handler, int priority, MenuExtension owner) { + String k = id.toLowerCase(); + byId.put(k, new Entry<>(handler, priority)); + keysByOwner.computeIfAbsent(owner, o -> new HashSet<>()).add(k); + } + + synchronized T byId(String id) { + Entry e = byId.get(id.toLowerCase()); + return e == null ? null : e.handler; + } + + synchronized boolean has(String id) { + return byId.containsKey(id.toLowerCase()); + } + + synchronized T auto() { + Entry best = null; + for (Entry e : byId.values()) { + if (best == null || e.priority > best.priority) best = e; + } + return best == null ? null : best.handler; + } + + synchronized Collection all() { + List list = new ArrayList<>(byId.size()); + for (Entry e : byId.values()) list.add(e.handler); + return Collections.unmodifiableList(list); + } + + synchronized void unregisterAll(MenuExtension owner) { + Set keys = keysByOwner.remove(owner); + if (keys == null) return; + for (String k : keys) byId.remove(k); + } + + private static final class Entry { + final T handler; + final int priority; + Entry(T handler, int priority) { this.handler = handler; this.priority = priority; } + } + } +} diff --git a/plugin/src/test/java/ru/abstractmenus/api/ProviderRegistryImplTest.java b/plugin/src/test/java/ru/abstractmenus/api/ProviderRegistryImplTest.java new file mode 100644 index 0000000..37c10f0 --- /dev/null +++ b/plugin/src/test/java/ru/abstractmenus/api/ProviderRegistryImplTest.java @@ -0,0 +1,113 @@ +package ru.abstractmenus.api; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import ru.abstractmenus.api.handler.EconomyHandler; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; + +class ProviderRegistryImplTest { + + private ProviderRegistryImpl registry; + private MenuExtension ownerA; + private MenuExtension ownerB; + + @BeforeEach + void setUp() { + registry = new ProviderRegistryImpl(); + ownerA = new DummyExtension("A"); + ownerB = new DummyExtension("B"); + } + + @Test + void register_singleProvider_resolvesByIdAndAuto() { + EconomyHandler vault = mock(EconomyHandler.class); + registry.registerEconomy("vault", vault, 50, ownerA); + + assertSame(vault, registry.economy()); + assertSame(vault, registry.economy("vault")); + assertEquals(1, registry.allEconomy().size()); + assertTrue(registry.hasEconomy("vault")); + } + + @Test + void auto_returnsNullWhenEmpty() { + assertNull(registry.economy()); + assertFalse(registry.hasEconomy("anything")); + } + + @Test + void auto_highestPriorityWins() { + EconomyHandler vault = mock(EconomyHandler.class); + EconomyHandler pp = mock(EconomyHandler.class); + registry.registerEconomy("vault", vault, 50, ownerA); + registry.registerEconomy("playerpoints", pp, 100, ownerA); + + assertSame(pp, registry.economy()); + } + + @Test + void auto_tieBreaksToFirstRegistered() { + EconomyHandler a = mock(EconomyHandler.class); + EconomyHandler b = mock(EconomyHandler.class); + registry.registerEconomy("alpha", a, 50, ownerA); + registry.registerEconomy("beta", b, 50, ownerA); + + assertSame(a, registry.economy()); + } + + @Test + void lookupById_caseInsensitive() { + EconomyHandler vault = mock(EconomyHandler.class); + registry.registerEconomy("Vault", vault, 50, ownerA); + + assertSame(vault, registry.economy("VAULT")); + assertSame(vault, registry.economy("vault")); + assertTrue(registry.hasEconomy("vAuLt")); + } + + @Test + void unregisterAll_removesOnlyThatOwner() { + EconomyHandler ea = mock(EconomyHandler.class); + EconomyHandler eb = mock(EconomyHandler.class); + registry.registerEconomy("a", ea, 50, ownerA); + registry.registerEconomy("b", eb, 50, ownerB); + + registry.unregisterAll(ownerA); + + assertNull(registry.economy("a")); + assertSame(eb, registry.economy("b")); + assertEquals(1, registry.allEconomy().size()); + } + + @Test + void overwrite_replacesPrevious() { + EconomyHandler old = mock(EconomyHandler.class); + EconomyHandler fresh = mock(EconomyHandler.class); + registry.registerEconomy("vault", old, 50, ownerA); + registry.registerEconomy("vault", fresh, 50, ownerB); + + assertSame(fresh, registry.economy("vault")); + } + + @Test + void sectionsAreIndependent() { + EconomyHandler e = mock(EconomyHandler.class); + registry.registerEconomy("e", e, 50, ownerA); + + assertNull(registry.permissions()); + assertNull(registry.levels()); + assertNull(registry.placeholders()); + assertNull(registry.skins()); + } + + // --- helper --- + + static class DummyExtension implements MenuExtension { + private final String name; + DummyExtension(String name) { this.name = name; } + @Override public void onEnable(AbstractMenusApi api) {} + @Override public String name() { return name; } + } +} From 35538dd984c686b45b38f84810225d736ff67963 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Thu, 23 Apr 2026 09:20:16 +0500 Subject: [PATCH 48/71] feat(plugin): AbstractMenusApiImpl exposes ProviderRegistryImpl Resolves intermediate broken build state from the two prior commits. Also fixes two StubApi test doubles (CoreExtensionTest, AddonManagerIntegrationTest) that needed to implement the new providers() abstract method. All 237 tests (229 baseline + 8 new ProviderRegistryImplTest) pass. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../main/java/ru/abstractmenus/api/AbstractMenusApiImpl.java | 3 +++ .../ru/abstractmenus/addon/AddonManagerIntegrationTest.java | 4 ++++ .../test/java/ru/abstractmenus/core/CoreExtensionTest.java | 4 ++++ 3 files changed, 11 insertions(+) diff --git a/plugin/src/main/java/ru/abstractmenus/api/AbstractMenusApiImpl.java b/plugin/src/main/java/ru/abstractmenus/api/AbstractMenusApiImpl.java index c1f2209..5b9b755 100644 --- a/plugin/src/main/java/ru/abstractmenus/api/AbstractMenusApiImpl.java +++ b/plugin/src/main/java/ru/abstractmenus/api/AbstractMenusApiImpl.java @@ -25,6 +25,7 @@ public final class AbstractMenusApiImpl implements AbstractMenusApi { private final TypeRegistry activators; private final TypeRegistry itemProperties; private final TypeRegistry> catalogs; + private final ProviderRegistry providers; public AbstractMenusApiImpl(AbstractMenus plugin) { this.plugin = plugin; @@ -33,6 +34,7 @@ public AbstractMenusApiImpl(AbstractMenus plugin) { this.activators = new TypeRegistryImpl<>(serializers); this.itemProperties = new TypeRegistryImpl<>(serializers); this.catalogs = new TypeRegistryImpl<>(serializers); + this.providers = new ProviderRegistryImpl(); } @Override public TypeRegistry actions() { return actions; } @@ -40,6 +42,7 @@ public AbstractMenusApiImpl(AbstractMenus plugin) { @Override public TypeRegistry activators() { return activators; } @Override public TypeRegistry itemProperties() { return itemProperties; } @Override public TypeRegistry> catalogs() { return catalogs; } + @Override public ProviderRegistry providers() { return providers; } @Override public NodeSerializers serializers() { return serializers; } diff --git a/plugin/src/test/java/ru/abstractmenus/addon/AddonManagerIntegrationTest.java b/plugin/src/test/java/ru/abstractmenus/addon/AddonManagerIntegrationTest.java index c2ad741..449d37a 100644 --- a/plugin/src/test/java/ru/abstractmenus/addon/AddonManagerIntegrationTest.java +++ b/plugin/src/test/java/ru/abstractmenus/addon/AddonManagerIntegrationTest.java @@ -9,6 +9,8 @@ import ru.abstractmenus.api.Catalog; import ru.abstractmenus.api.MenuExtension; import ru.abstractmenus.api.Rule; +import ru.abstractmenus.api.ProviderRegistry; +import ru.abstractmenus.api.ProviderRegistryImpl; import ru.abstractmenus.api.TypeRegistry; import ru.abstractmenus.api.TypeRegistryImpl; import ru.abstractmenus.api.inventory.ItemProperty; @@ -153,12 +155,14 @@ private static class StubApi implements AbstractMenusApi { private final TypeRegistry activators = new TypeRegistryImpl<>(serializers); private final TypeRegistry itemProperties = new TypeRegistryImpl<>(serializers); private final TypeRegistry> catalogs = new TypeRegistryImpl<>(serializers); + private final ProviderRegistry providers = new ProviderRegistryImpl(); @Override public TypeRegistry actions() { return actions; } @Override public TypeRegistry rules() { return rules; } @Override public TypeRegistry activators() { return activators; } @Override public TypeRegistry itemProperties() { return itemProperties; } @Override public TypeRegistry> catalogs() { return catalogs; } + @Override public ProviderRegistry providers() { return providers; } @Override public NodeSerializers serializers() { return serializers; } @Override public VariableManager variables() { return null; } @Override public org.bukkit.plugin.Plugin getPlugin() { return null; } diff --git a/plugin/src/test/java/ru/abstractmenus/core/CoreExtensionTest.java b/plugin/src/test/java/ru/abstractmenus/core/CoreExtensionTest.java index fce7108..d597de3 100644 --- a/plugin/src/test/java/ru/abstractmenus/core/CoreExtensionTest.java +++ b/plugin/src/test/java/ru/abstractmenus/core/CoreExtensionTest.java @@ -13,6 +13,8 @@ import ru.abstractmenus.api.Catalog; import ru.abstractmenus.api.MenuExtension; import ru.abstractmenus.api.Rule; +import ru.abstractmenus.api.ProviderRegistry; +import ru.abstractmenus.api.ProviderRegistryImpl; import ru.abstractmenus.api.TypeRegistry; import ru.abstractmenus.api.TypeRegistryImpl; import ru.abstractmenus.api.inventory.ItemProperty; @@ -143,12 +145,14 @@ private static final class StubApi implements AbstractMenusApi { private final TypeRegistry activators = new TypeRegistryImpl<>(serializers); private final TypeRegistry itemProperties = new TypeRegistryImpl<>(serializers); private final TypeRegistry> catalogs = new TypeRegistryImpl<>(serializers); + private final ProviderRegistry providers = new ProviderRegistryImpl(); @Override public TypeRegistry actions() { return actions; } @Override public TypeRegistry rules() { return rules; } @Override public TypeRegistry activators() { return activators; } @Override public TypeRegistry itemProperties() { return itemProperties; } @Override public TypeRegistry> catalogs() { return catalogs; } + @Override public ProviderRegistry providers() { return providers; } @Override public NodeSerializers serializers() { return serializers; } @Override public VariableManager variables() { throw new UnsupportedOperationException(); } From 23ea6af3a6af8a84d78943dfce6b66c51fef348f Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Thu, 23 Apr 2026 09:23:02 +0500 Subject: [PATCH 49/71] =?UTF-8?q?feat(core):=20CoreProvidersBundle=20?= =?UTF-8?q?=E2=80=94=20registerProviders=20via=20new=20SPI=20(not=20yet=20?= =?UTF-8?q?invoked)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirrors AbstractMenus.registerProviders exactly — same guards, same handler constructors, same fallbacks. CoreExtension wires this in the next task. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../core/CoreProvidersBundle.java | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 plugin/src/main/java/ru/abstractmenus/core/CoreProvidersBundle.java diff --git a/plugin/src/main/java/ru/abstractmenus/core/CoreProvidersBundle.java b/plugin/src/main/java/ru/abstractmenus/core/CoreProvidersBundle.java new file mode 100644 index 0000000..aa63fc6 --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/core/CoreProvidersBundle.java @@ -0,0 +1,117 @@ +package ru.abstractmenus.core; + +import net.luckperms.api.LuckPerms; +import net.milkbowl.vault.economy.Economy; +import org.bukkit.Bukkit; +import org.bukkit.plugin.RegisteredServiceProvider; +import ru.abstractmenus.AbstractMenus; +import ru.abstractmenus.api.AbstractMenusApi; +import ru.abstractmenus.api.Logger; +import ru.abstractmenus.api.MenuExtension; +import ru.abstractmenus.handlers.EconomyVaultHandler; +import ru.abstractmenus.handlers.LevelDefaultHandler; +import ru.abstractmenus.handlers.LuckPermsHandler; +import ru.abstractmenus.handlers.PermissionDefaultHandler; +import ru.abstractmenus.handlers.SkinsRestorerHandler; +import ru.abstractmenus.handlers.placeholder.PlaceholderCustomHandler; +import ru.abstractmenus.handlers.placeholder.PlaceholderDefaultHandler; + +/** + * Registers built-in handler providers (economy, permissions, levels, + * placeholders, skins). Mirrors the old {@code AbstractMenus.registerProviders} + * behaviour — same conditions, same priorities — but through the + * new {@link ru.abstractmenus.api.ProviderRegistry} SPI. + * + *

All core providers register at priority 50 so addons can override by + * registering at priority > 50. + */ +final class CoreProvidersBundle { + + private static final int CORE_PRIORITY = 50; + + void register(AbstractMenusApi api, MenuExtension owner) { + AbstractMenus plugin = AbstractMenus.instance(); + + // ---- Economy -------------------------------------------------------- + if (AbstractMenus.checkDependency("Vault")) { + RegisteredServiceProvider economyProvider = + Bukkit.getServicesManager().getRegistration(Economy.class); + + if (economyProvider != null) { + api.providers().registerEconomy( + "vault", + new EconomyVaultHandler(economyProvider.getProvider()), + CORE_PRIORITY, + owner); + Logger.info("Registered core economy provider: vault"); + } else { + Logger.warning("Economy plugin doesn't installed"); + } + } else { + Logger.warning("Vault doesn't installed. Economy actions and rules won't work"); + } + + // ---- Permissions ---------------------------------------------------- + if (AbstractMenus.checkDependency("LuckPerms")) { + RegisteredServiceProvider provider = + Bukkit.getServicesManager().getRegistration(LuckPerms.class); + + if (provider != null) { + api.providers().registerPermissions( + "luckperms", + new LuckPermsHandler(provider.getProvider()), + CORE_PRIORITY, + owner); + Logger.info("Using LuckPerms"); + } else { + Logger.severe("Cannot find registered LuckPerms service"); + } + } else { + api.providers().registerPermissions( + "default", + new PermissionDefaultHandler(plugin), + CORE_PRIORITY, + owner); + Logger.info("Using bundled temporary permissions manager"); + Logger.warning("LuckPerms doesn't installed. After reload all assigned permissions will be removed"); + } + + // ---- Levels --------------------------------------------------------- + api.providers().registerLevels( + "default", + new LevelDefaultHandler(), + CORE_PRIORITY, + owner); + + // ---- Placeholders --------------------------------------------------- + if (AbstractMenus.checkDependency("PlaceholderAPI")) { + PlaceholderCustomHandler papiHandler = new PlaceholderCustomHandler(); + api.providers().registerPlaceholders( + "placeholderapi", + papiHandler, + CORE_PRIORITY, + owner); + papiHandler.registerAll(); + Logger.info("Using PlaceholderAPI"); + } else { + PlaceholderDefaultHandler defaultHandler = new PlaceholderDefaultHandler(); + api.providers().registerPlaceholders( + "default", + defaultHandler, + CORE_PRIORITY, + owner); + defaultHandler.registerAll(); + Logger.info("Using bundled placeholders"); + } + + // ---- Skins ---------------------------------------------------------- + if (AbstractMenus.checkDependency("SkinsRestorer")) { + api.providers().registerSkins( + "skinsrestorer", + new SkinsRestorerHandler(plugin.isProxyMode, plugin), + CORE_PRIORITY, + owner); + Logger.info("Using SkinsRestorer as skins provider"); + } + } +} From 99a335ff4404c75049ade4339117a370e86d6098 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Thu, 23 Apr 2026 09:24:23 +0500 Subject: [PATCH 50/71] feat(core): invoke CoreProvidersBundle from CoreExtension.onEnable Transitional dual-path: old Handlers.set* (in AbstractMenus.registerProviders) and new api.providers().register* (in this bundle) both run. Callers migrate in the next task, old path deleted in the task after. Co-Authored-By: Claude Opus 4.7 (1M context) --- plugin/src/main/java/ru/abstractmenus/core/CoreExtension.java | 1 + 1 file changed, 1 insertion(+) diff --git a/plugin/src/main/java/ru/abstractmenus/core/CoreExtension.java b/plugin/src/main/java/ru/abstractmenus/core/CoreExtension.java index 42cc294..8d5649d 100644 --- a/plugin/src/main/java/ru/abstractmenus/core/CoreExtension.java +++ b/plugin/src/main/java/ru/abstractmenus/core/CoreExtension.java @@ -33,5 +33,6 @@ public void onEnable(AbstractMenusApi api) { new CoreItemPropsBundle().register(api, this); new CoreActivatorsBundle().register(api, this); new CoreCatalogsBundle().register(api, this); + new CoreProvidersBundle().register(api, this); } } From ce082fdf543e1ffc2b373ced5bab69d070d8c113 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Thu, 23 Apr 2026 10:39:57 +0500 Subject: [PATCH 51/71] refactor(plugin): migrate Handlers.* call sites to AbstractMenusApi.providers().* MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Covers ~78 files across data/, services/, menu/, command/, extractors/, datatype/. Handlers.set* calls in AbstractMenus.registerProviders() remain for one more commit — removed in the next task. Test suites that relied on mutating the static Handlers facade (TestActionCommandBehavior, TestPropNameLorePrecompute, TestCompoundDataTypes, TestPrimitiveDataTypes) now route through a new ApiTestSupport helper that stubs Bukkit.getServicesManager() and installs their placeholder handler via the ProviderRegistry so production code paths (AbstractMenusApi.get()...) resolve correctly under unit tests. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../java/ru/abstractmenus/AbstractMenus.java | 2 +- .../ru/abstractmenus/command/Command.java | 4 +- .../data/actions/ActionBookOpen.java | 4 +- .../data/actions/ActionBroadcast.java | 12 +- .../data/actions/ActionBungeeConnect.java | 4 +- .../data/actions/ActionCommand.java | 6 +- .../data/actions/ActionGroupAdd.java | 6 +- .../data/actions/ActionGroupRemove.java | 6 +- .../data/actions/ActionInputChat.java | 4 +- .../data/actions/ActionLevelGive.java | 4 +- .../data/actions/ActionLevelTake.java | 4 +- .../abstractmenus/data/actions/ActionLog.java | 4 +- .../actions/ActionLuckPermsMetaRemove.java | 4 +- .../data/actions/ActionLuckPermsMetaSet.java | 6 +- .../data/actions/ActionMenuOpen.java | 4 +- .../data/actions/ActionMenuOpenCtx.java | 4 +- .../data/actions/ActionMessage.java | 12 +- .../data/actions/ActionMiniMessage.java | 4 +- .../data/actions/ActionMoneyGive.java | 6 +- .../data/actions/ActionMoneyTake.java | 6 +- .../data/actions/ActionPermissionGive.java | 6 +- .../data/actions/ActionPermissionRemove.java | 6 +- .../data/actions/ActionSkinReset.java | 4 +- .../data/actions/ActionSkinSet.java | 8 +- .../data/actions/ActionXpGive.java | 4 +- .../data/actions/ActionXpTake.java | 4 +- .../data/actions/var/ActionVarDec.java | 6 +- .../data/actions/var/ActionVarDiv.java | 6 +- .../data/actions/var/ActionVarInc.java | 6 +- .../data/actions/var/ActionVarMul.java | 6 +- .../data/actions/var/ActionVarRem.java | 6 +- .../data/actions/var/ActionVarSet.java | 10 +- .../data/actions/varp/ActionVarpDec.java | 4 +- .../data/actions/varp/ActionVarpDiv.java | 4 +- .../data/actions/varp/ActionVarpInc.java | 4 +- .../data/actions/varp/ActionVarpMul.java | 4 +- .../data/actions/varp/ActionVarpRem.java | 4 +- .../data/actions/varp/ActionVarpSet.java | 8 +- .../actions/wrappers/ActionPlayerScope.java | 4 +- .../data/activators/OpenChat.java | 4 +- .../data/activators/OpenChatContains.java | 4 +- .../data/activators/OpenClickEntity.java | 4 +- .../data/activators/OpenRegionEnter.java | 4 +- .../data/activators/OpenRegionLeave.java | 4 +- .../data/activators/OpenShiftClickEntity.java | 4 +- .../data/activators/OpenSign.java | 4 +- .../data/catalogs/SliceCatalog.java | 4 +- .../comparator/LegacyValueComparator.java | 10 +- .../comparator/ModernValueComparator.java | 4 +- .../data/properties/PropBookData.java | 8 +- .../data/properties/PropEquipItem.java | 4 +- .../data/properties/PropHDB.java | 4 +- .../data/properties/PropItemsAdder.java | 4 +- .../data/properties/PropLore.java | 4 +- .../data/properties/PropLoreLight.java | 4 +- .../data/properties/PropMmoItem.java | 4 +- .../data/properties/PropName.java | 4 +- .../data/properties/PropNameLight.java | 4 +- .../data/properties/PropOraxen.java | 4 +- .../data/properties/PropSerialized.java | 4 +- .../data/properties/PropSkullOwner.java | 4 +- .../data/properties/PropTexture.java | 4 +- .../data/rules/RuleBungeeIsOnline.java | 4 +- .../data/rules/RuleBungeeOnline.java | 4 +- .../data/rules/RuleExistVar.java | 6 +- .../data/rules/RuleExistVarp.java | 4 +- .../abstractmenus/data/rules/RuleGroup.java | 6 +- .../ru/abstractmenus/data/rules/RuleJS.java | 4 +- .../abstractmenus/data/rules/RuleLevel.java | 4 +- .../abstractmenus/data/rules/RuleMoney.java | 6 +- .../data/rules/RulePermission.java | 6 +- .../data/rules/RulePlayerIsOnline.java | 4 +- .../abstractmenus/data/rules/RuleRegion.java | 4 +- .../ru/abstractmenus/data/rules/RuleXp.java | 4 +- .../data/rules/logical/RulePlayerScope.java | 4 +- .../ru/abstractmenus/datatype/DataType.java | 4 +- .../extractors/PlayerExtractor.java | 4 +- .../ru/abstractmenus/menu/AbstractMenu.java | 4 +- .../actions/TestActionCommandBehavior.java | 24 ++-- .../TestPropNameLorePrecompute.java | 10 +- .../datatype/TestCompoundDataTypes.java | 10 +- .../datatype/TestPrimitiveDataTypes.java | 10 +- .../testsupport/ApiTestSupport.java | 121 ++++++++++++++++++ 83 files changed, 343 insertions(+), 216 deletions(-) create mode 100644 plugin/src/test/java/ru/abstractmenus/testsupport/ApiTestSupport.java diff --git a/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java b/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java index 0d1a72d..db4d3e0 100644 --- a/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java +++ b/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java @@ -286,7 +286,7 @@ private void registerProviders() { Logger.info("Using bundled placeholders"); } - Handlers.getPlaceholderHandler().registerAll(); + AbstractMenusApi.get().providers().placeholders().registerAll(); if (checkDependency("SkinsRestorer")) { Handlers.setSkinHandler(new SkinsRestorerHandler(isProxyMode, this)); diff --git a/plugin/src/main/java/ru/abstractmenus/command/Command.java b/plugin/src/main/java/ru/abstractmenus/command/Command.java index df5f263..742f805 100644 --- a/plugin/src/main/java/ru/abstractmenus/command/Command.java +++ b/plugin/src/main/java/ru/abstractmenus/command/Command.java @@ -4,7 +4,7 @@ import lombok.Setter; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.text.Colors; import ru.abstractmenus.command.args.Argument; import ru.abstractmenus.hocon.api.ConfigNode; @@ -68,7 +68,7 @@ public void execute(CommandSender sender, List args) { } catch (Throwable t) { if (arg.getDef() != null && sender instanceof Player) { Player player = (Player) sender; - String def = Handlers.getPlaceholderHandler().replace(player, arg.getDef()); + String def = AbstractMenusApi.get().providers().placeholders().replace(player, arg.getDef()); try { Object defObj = arg.parse(sender, def); diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionBookOpen.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionBookOpen.java index 7c5acbb..308f6aa 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionBookOpen.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionBookOpen.java @@ -10,7 +10,7 @@ import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.Action; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; public class ActionBookOpen implements Action { @@ -23,7 +23,7 @@ private ActionBookOpen(BookData bookData) { @Override public void activate(Player player, Menu menu, Item clickedItem) { BookData data = bookData.clone(); - PlaceholderHandler handler = Handlers.getPlaceholderHandler(); + PlaceholderHandler handler = AbstractMenusApi.get().providers().placeholders(); data.setAuthor(handler.replace(player, bookData.getAuthor())); data.setTitle(handler.replace(player, bookData.getTitle())); diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionBroadcast.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionBroadcast.java index 8d75ec8..c10e963 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionBroadcast.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionBroadcast.java @@ -11,7 +11,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.text.Colors; import ru.abstractmenus.nms.actionbar.ActionBar; import ru.abstractmenus.nms.title.Title; @@ -70,7 +70,7 @@ private void setStay(TypeInt stay) { public void activate(Player player, Menu menu, Item clickedItem) { if (player != null) { if (chatMessages != null) { - List replaced = Handlers.getPlaceholderHandler().replace(player, chatMessages); + List replaced = AbstractMenusApi.get().providers().placeholders().replace(player, chatMessages); for (Player p : Bukkit.getOnlinePlayers()) { MiniMessageUtil.sendParsed(replaced, p); @@ -79,7 +79,7 @@ public void activate(Player player, Menu menu, Item clickedItem) { if (json != null) { BaseComponent[] component = ComponentSerializer.parse( - Handlers.getPlaceholderHandler().replace(player, json)); + AbstractMenusApi.get().providers().placeholders().replace(player, json)); if (component != null) { for (Player p : Bukkit.getOnlinePlayers()) @@ -88,7 +88,7 @@ public void activate(Player player, Menu menu, Item clickedItem) { } if (actionbar != null) { - String replaced = Handlers.getPlaceholderHandler().replace(player, actionbar); + String replaced = AbstractMenusApi.get().providers().placeholders().replace(player, actionbar); ActionBar bar = ActionBar.create(); for (Player p : Bukkit.getOnlinePlayers()) @@ -97,10 +97,10 @@ public void activate(Player player, Menu menu, Item clickedItem) { if (!this.title.isEmpty() || !this.subtitle.isEmpty()) { String title = MiniMessageUtil.parseToLegacy( - Handlers.getPlaceholderHandler().replace(player, this.title) + AbstractMenusApi.get().providers().placeholders().replace(player, this.title) ); String subtitle = MiniMessageUtil.parseToLegacy( - Handlers.getPlaceholderHandler().replace(player, this.subtitle) + AbstractMenusApi.get().providers().placeholders().replace(player, this.subtitle) ); Title t = new Title( title, subtitle, diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionBungeeConnect.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionBungeeConnect.java index 3dfec52..290e919 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionBungeeConnect.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionBungeeConnect.java @@ -6,7 +6,7 @@ import org.bukkit.entity.Player; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.services.BungeeManager; import ru.abstractmenus.api.Action; @@ -20,7 +20,7 @@ private ActionBungeeConnect(String serverName) { @Override public void activate(Player player, Menu menu, Item clickedItem) { - String replaced = Handlers.getPlaceholderHandler().replace(player, serverName); + String replaced = AbstractMenusApi.get().providers().placeholders().replace(player, serverName); BungeeManager.instance().sendPluginMessage(player, "Connect", replaced); } diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionCommand.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionCommand.java index 426ca03..bedb95c 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionCommand.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionCommand.java @@ -5,7 +5,7 @@ import org.bukkit.entity.Player; import ru.abstractmenus.AbstractMenus; import ru.abstractmenus.api.Action; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.hocon.api.ConfigNode; @@ -37,7 +37,7 @@ private void setIgnorePlaceholder(boolean isIgnorePlaceholder) { public void activate(Player player, Menu menu, Item clickedItem) { for (String command : playerCommands) { if (command != null) { - String resultCommand = isIgnorePlaceholder ? command : Handlers.getPlaceholderHandler().replace(player, command); + String resultCommand = isIgnorePlaceholder ? command : AbstractMenusApi.get().providers().placeholders().replace(player, command); player.performCommand(resultCommand); } } @@ -46,7 +46,7 @@ public void activate(Player player, Menu menu, Item clickedItem) { Bukkit.getServer().getGlobalRegionScheduler().execute(AbstractMenus.instance(), () -> { for (String command : consoleCommands) { if (command != null) { - String resultCommand = isIgnorePlaceholder ? command : Handlers.getPlaceholderHandler().replace(player, command); + String resultCommand = isIgnorePlaceholder ? command : AbstractMenusApi.get().providers().placeholders().replace(player, command); Bukkit.dispatchCommand(Bukkit.getConsoleSender(), resultCommand); } } diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionGroupAdd.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionGroupAdd.java index 9ea7333..464a29f 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionGroupAdd.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionGroupAdd.java @@ -7,7 +7,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; public class ActionGroupAdd implements Action { @@ -19,8 +19,8 @@ private ActionGroupAdd(String group) { @Override public void activate(Player player, Menu menu, Item clickedItem) { - String group = Handlers.getPlaceholderHandler().replace(player, this.group); - Handlers.getPermissionsHandler().addGroup(player, group); + String group = AbstractMenusApi.get().providers().placeholders().replace(player, this.group); + AbstractMenusApi.get().providers().permissions().addGroup(player, group); } public static class Serializer implements NodeSerializer { diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionGroupRemove.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionGroupRemove.java index 1866e3f..2589caf 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionGroupRemove.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionGroupRemove.java @@ -7,7 +7,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; public class ActionGroupRemove implements Action { @@ -19,8 +19,8 @@ private ActionGroupRemove(String group) { @Override public void activate(Player player, Menu menu, Item clickedItem) { - String groupName = Handlers.getPlaceholderHandler().replace(player, group); - Handlers.getPermissionsHandler().removeGroup(player, groupName); + String groupName = AbstractMenusApi.get().providers().placeholders().replace(player, group); + AbstractMenusApi.get().providers().permissions().removeGroup(player, groupName); } public static class Serializer implements NodeSerializer { diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionInputChat.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionInputChat.java index 0cf633a..ff4a5d3 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionInputChat.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionInputChat.java @@ -3,7 +3,7 @@ import lombok.Getter; import org.bukkit.entity.Player; import ru.abstractmenus.api.Action; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.variables.Var; @@ -34,7 +34,7 @@ public ActionInputChat(String varName, boolean global, String cancelWord, @Override public void activate(Player player, Menu m, Item clickedItem) { - String name = Handlers.getPlaceholderHandler().replace(player, varName); + String name = AbstractMenusApi.get().providers().placeholders().replace(player, varName); InputAction action = new InputAction(player, name, global, cancelWord, onInput, onCancel); MenuManager.instance().saveInputAction(action); m.close(player); diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionLevelGive.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionLevelGive.java index c4fb509..1a66c68 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionLevelGive.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionLevelGive.java @@ -8,7 +8,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; public class ActionLevelGive implements Action { @@ -20,7 +20,7 @@ private ActionLevelGive(TypeInt level) { @Override public void activate(Player player, Menu menu, Item clickedItem) { - Handlers.getLevelHandler().giveLevel(player, level.getInt(player, menu)); + AbstractMenusApi.get().providers().levels().giveLevel(player, level.getInt(player, menu)); } public static class Serializer implements NodeSerializer { diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionLevelTake.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionLevelTake.java index f2c03fd..4cc2281 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionLevelTake.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionLevelTake.java @@ -9,7 +9,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; public class ActionLevelTake implements Action { @@ -21,7 +21,7 @@ private ActionLevelTake(TypeInt level) { @Override public void activate(Player player, Menu menu, Item clickedItem) { - Handlers.getLevelHandler().takeLevel(player, level.getInt(player, menu)); + AbstractMenusApi.get().providers().levels().takeLevel(player, level.getInt(player, menu)); } public static class Serializer implements NodeSerializer { diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionLog.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionLog.java index 53a7a3d..7ad1bcb 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionLog.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionLog.java @@ -2,7 +2,7 @@ import org.bukkit.entity.Player; import ru.abstractmenus.api.Action; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.Logger; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; @@ -20,7 +20,7 @@ private ActionLog(String message) { @Override public void activate(Player player, Menu menu, Item clickedItem) { - Logger.info(Handlers.getPlaceholderHandler().replace(player, message)); + Logger.info(AbstractMenusApi.get().providers().placeholders().replace(player, message)); } public static class Serializer implements NodeSerializer { diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionLuckPermsMetaRemove.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionLuckPermsMetaRemove.java index c27a3a2..88a1260 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionLuckPermsMetaRemove.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionLuckPermsMetaRemove.java @@ -4,7 +4,7 @@ import lombok.RequiredArgsConstructor; import org.bukkit.entity.Player; import ru.abstractmenus.api.Action; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.handlers.LuckPermsHandler; @@ -22,7 +22,7 @@ public class ActionLuckPermsMetaRemove implements Action { @Override public void activate(Player player, Menu menu, Item clickedItem) { metaList.forEach(metaKey -> { - if (Handlers.getPermissionsHandler() instanceof LuckPermsHandler handler) { + if (AbstractMenusApi.get().providers().permissions() instanceof LuckPermsHandler handler) { handler.removeMeta(player, metaKey); } }); diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionLuckPermsMetaSet.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionLuckPermsMetaSet.java index 39626f9..377a474 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionLuckPermsMetaSet.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionLuckPermsMetaSet.java @@ -4,7 +4,7 @@ import lombok.Setter; import org.bukkit.entity.Player; import ru.abstractmenus.api.Action; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.data.properties.PropLPMeta; @@ -24,8 +24,8 @@ public class ActionLuckPermsMetaSet implements Action { @Override public void activate(Player player, Menu menu, Item clickedItem) { metaList.forEach(meta -> { - String replacedValue = isIgnorePlaceholder ? meta.getValue() : Handlers.getPlaceholderHandler().replace(player, meta.getValue()); - if (Handlers.getPermissionsHandler() instanceof LuckPermsHandler handler) { + String replacedValue = isIgnorePlaceholder ? meta.getValue() : AbstractMenusApi.get().providers().placeholders().replace(player, meta.getValue()); + if (AbstractMenusApi.get().providers().permissions() instanceof LuckPermsHandler handler) { handler.addMeta(player, meta.getKey(), replacedValue); } }); diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMenuOpen.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMenuOpen.java index e23d80f..aa39bbd 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMenuOpen.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMenuOpen.java @@ -8,7 +8,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.services.MenuManager; public class ActionMenuOpen implements Action { @@ -21,7 +21,7 @@ private ActionMenuOpen(String menu) { @Override public void activate(Player player, Menu menu, Item clickedItem) { - String name = Handlers.getPlaceholderHandler().replace(player, this.menu); + String name = AbstractMenusApi.get().providers().placeholders().replace(player, this.menu); Menu menuToOpen = MenuManager.instance().getMenu(name); if (menuToOpen != null) diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMenuOpenCtx.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMenuOpenCtx.java index 3b1958a..f332513 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMenuOpenCtx.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMenuOpenCtx.java @@ -3,7 +3,7 @@ import org.bukkit.entity.Player; import ru.abstractmenus.api.Action; import ru.abstractmenus.api.Activator; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.Logger; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; @@ -22,7 +22,7 @@ private ActionMenuOpenCtx(String menu) { @Override public void activate(Player player, Menu menu, Item clickedItem) { - String name = Handlers.getPlaceholderHandler().replace(player, this.menu); + String name = AbstractMenusApi.get().providers().placeholders().replace(player, this.menu); Menu menuToOpen = MenuManager.instance().getMenu(name); if (menuToOpen != null) { diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMessage.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMessage.java index 470ca6f..dddf207 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMessage.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMessage.java @@ -10,7 +10,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.nms.actionbar.ActionBar; import ru.abstractmenus.nms.title.Title; import ru.abstractmenus.api.text.Colors; @@ -69,29 +69,29 @@ private void setStay(TypeInt stay) { public void activate(Player player, Menu menu, Item clickedItem) { if (player != null) { if (chatMessages != null) { - List replaced = Handlers.getPlaceholderHandler().replace(player, chatMessages); + List replaced = AbstractMenusApi.get().providers().placeholders().replace(player, chatMessages); MiniMessageUtil.sendParsed(replaced, player); } if (json != null) { BaseComponent[] component = ComponentSerializer.parse( - Handlers.getPlaceholderHandler().replace(player, json)); + AbstractMenusApi.get().providers().placeholders().replace(player, json)); if (component != null) player.spigot().sendMessage(component); } if (actionbar != null) { - String replaced = Handlers.getPlaceholderHandler().replace(player, actionbar); + String replaced = AbstractMenusApi.get().providers().placeholders().replace(player, actionbar); ActionBar.create().send(player, MiniMessageUtil.parseToLegacy(replaced)); } if (!this.title.isEmpty() || !this.subtitle.isEmpty()) { String title = MiniMessageUtil.parseToLegacy( - Handlers.getPlaceholderHandler().replace(player, this.title) + AbstractMenusApi.get().providers().placeholders().replace(player, this.title) ); String subtitle = MiniMessageUtil.parseToLegacy( - Handlers.getPlaceholderHandler().replace(player, this.subtitle) + AbstractMenusApi.get().providers().placeholders().replace(player, this.subtitle) ); new Title( diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMiniMessage.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMiniMessage.java index 15cacb1..5cfbc30 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMiniMessage.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMiniMessage.java @@ -7,7 +7,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.util.MiniMessageUtil; import java.util.Collections; @@ -22,7 +22,7 @@ private ActionMiniMessage(String message) { @Override public void activate(Player player, Menu menu, Item clickedItem) { - String replaced = Handlers.getPlaceholderHandler().replace(player, message); + String replaced = AbstractMenusApi.get().providers().placeholders().replace(player, message); MiniMessageUtil.sendParsed(Collections.singletonList(replaced), player); } diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMoneyGive.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMoneyGive.java index 7439f3a..5710837 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMoneyGive.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMoneyGive.java @@ -9,7 +9,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; public class ActionMoneyGive implements Action { @@ -21,8 +21,8 @@ private ActionMoneyGive(TypeDouble money) { @Override public void activate(Player player, Menu menu, Item clickedItem) { - if (Handlers.getEconomyHandler() != null) { - Handlers.getEconomyHandler().giveBalance(player, money.getDouble(player, menu)); + if (AbstractMenusApi.get().providers().economy() != null) { + AbstractMenusApi.get().providers().economy().giveBalance(player, money.getDouble(player, menu)); } } diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMoneyTake.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMoneyTake.java index e5f92bf..c9b543f 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMoneyTake.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMoneyTake.java @@ -9,7 +9,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; public class ActionMoneyTake implements Action { @@ -21,8 +21,8 @@ private ActionMoneyTake(TypeDouble money) { @Override public void activate(Player player, Menu menu, Item clickedItem) { - if (Handlers.getEconomyHandler() != null) { - Handlers.getEconomyHandler().takeBalance(player, money.getDouble(player, menu)); + if (AbstractMenusApi.get().providers().economy() != null) { + AbstractMenusApi.get().providers().economy().takeBalance(player, money.getDouble(player, menu)); } } diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPermissionGive.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPermissionGive.java index fb54cbf..f2e5667 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPermissionGive.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPermissionGive.java @@ -4,7 +4,7 @@ import lombok.Setter; import org.bukkit.entity.Player; import ru.abstractmenus.api.Action; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.hocon.api.ConfigNode; @@ -27,8 +27,8 @@ public void setIgnorePlaceholder(boolean ignorePlaceholder) { @Override public void activate(Player player, Menu menu, Item clickedItem) { permissions.forEach(perm -> { - String replaced = isIgnorePlaceholder ? perm : Handlers.getPlaceholderHandler().replace(player, perm); - Handlers.getPermissionsHandler().addPermission(player, replaced); + String replaced = isIgnorePlaceholder ? perm : AbstractMenusApi.get().providers().placeholders().replace(player, perm); + AbstractMenusApi.get().providers().permissions().addPermission(player, replaced); }); } diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPermissionRemove.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPermissionRemove.java index a7dfd23..4afe92b 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPermissionRemove.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionPermissionRemove.java @@ -8,7 +8,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import java.util.List; @@ -23,8 +23,8 @@ private ActionPermissionRemove(List permissions) { @Override public void activate(Player player, Menu menu, Item clickedItem) { permissions.forEach(perm -> { - String replaced = Handlers.getPlaceholderHandler().replace(player, perm); - Handlers.getPermissionsHandler().removePermission(player, replaced); + String replaced = AbstractMenusApi.get().providers().placeholders().replace(player, perm); + AbstractMenusApi.get().providers().permissions().removePermission(player, replaced); }); } diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionSkinReset.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionSkinReset.java index fb5d91d..ae71ab0 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionSkinReset.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionSkinReset.java @@ -9,7 +9,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; public class ActionSkinReset implements Action { @@ -21,7 +21,7 @@ private ActionSkinReset(TypeBool reset) { public void activate(Player player, Menu menu, Item clickedItem) { if (reset.getBool(player, menu)) { - Handlers.getSkinHandler().resetSkin(player); + AbstractMenusApi.get().providers().skins().resetSkin(player); } } diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionSkinSet.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionSkinSet.java index 8036a43..ef7631e 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionSkinSet.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionSkinSet.java @@ -8,7 +8,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; public class ActionSkinSet implements Action { @@ -21,9 +21,9 @@ private ActionSkinSet(String texture, String signature) { } public void activate(Player player, Menu menu, Item clickedItem) { - Handlers.getSkinHandler().setSkin(player, - Handlers.getPlaceholderHandler().replace(player, texture), - Handlers.getPlaceholderHandler().replace(player, signature)); + AbstractMenusApi.get().providers().skins().setSkin(player, + AbstractMenusApi.get().providers().placeholders().replace(player, texture), + AbstractMenusApi.get().providers().placeholders().replace(player, signature)); } public static class Serializer implements NodeSerializer { diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionXpGive.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionXpGive.java index 89c4b1e..7641dc6 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionXpGive.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionXpGive.java @@ -9,7 +9,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; public class ActionXpGive implements Action { @@ -21,7 +21,7 @@ private ActionXpGive(TypeInt xp) { @Override public void activate(Player player, Menu menu, Item clickedItem) { - Handlers.getLevelHandler().giveXp(player, xp.getInt(player, menu)); + AbstractMenusApi.get().providers().levels().giveXp(player, xp.getInt(player, menu)); } public static class Serializer implements NodeSerializer { diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionXpTake.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionXpTake.java index 956e983..a3a9a10 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionXpTake.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionXpTake.java @@ -8,7 +8,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; public class ActionXpTake implements Action { @@ -20,7 +20,7 @@ private ActionXpTake(TypeInt xp) { @Override public void activate(Player player, Menu menu, Item clickedItem) { - Handlers.getLevelHandler().takeXp(player, xp.getInt(player, menu)); + AbstractMenusApi.get().providers().levels().takeXp(player, xp.getInt(player, menu)); } public static class Serializer implements NodeSerializer { diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarDec.java b/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarDec.java index 4789e8d..8fb3990 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarDec.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarDec.java @@ -7,7 +7,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.variables.VarNumData; import ru.abstractmenus.variables.VariableManagerImpl; @@ -24,14 +24,14 @@ private ActionVarDec(List dataList) { public void activate(Player p, Menu menu, Item clickedItem) { for (VarNumData data : dataList) { - String varName = Handlers.getPlaceholderHandler().replace(p, data.getName()); + String varName = AbstractMenusApi.get().providers().placeholders().replace(p, data.getName()); double value = data.getValue().getDouble(p, menu); Function func = num -> num - value; if (data.getPlayer() == null) { VariableManagerImpl.instance().modifyNumericGlobal(varName, func); } else { - String playerName = Handlers.getPlaceholderHandler().replace(p, data.getPlayer()); + String playerName = AbstractMenusApi.get().providers().placeholders().replace(p, data.getPlayer()); VariableManagerImpl.instance().modifyNumericPersonal(playerName, varName, func); } } diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarDiv.java b/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarDiv.java index ce9496b..c4131f5 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarDiv.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarDiv.java @@ -8,7 +8,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.variables.VarNumData; import ru.abstractmenus.variables.VariableManagerImpl; @@ -25,7 +25,7 @@ private ActionVarDiv(List dataList) { public void activate(Player p, Menu menu, Item clickedItem) { for (VarNumData data : dataList) { - String varName = Handlers.getPlaceholderHandler().replace(p, data.getName()); + String varName = AbstractMenusApi.get().providers().placeholders().replace(p, data.getName()); double value = data.getValue().getDouble(p, menu); if (value == 0) { @@ -38,7 +38,7 @@ public void activate(Player p, Menu menu, Item clickedItem) { if (data.getPlayer() == null) { VariableManagerImpl.instance().modifyNumericGlobal(varName, func); } else { - String playerName = Handlers.getPlaceholderHandler().replace(p, data.getPlayer()); + String playerName = AbstractMenusApi.get().providers().placeholders().replace(p, data.getPlayer()); VariableManagerImpl.instance().modifyNumericPersonal(playerName, varName, func); } } diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarInc.java b/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarInc.java index cc98032..363a635 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarInc.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarInc.java @@ -7,7 +7,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.variables.VarNumData; import ru.abstractmenus.variables.VariableManagerImpl; @@ -24,14 +24,14 @@ private ActionVarInc(List dataList) { public void activate(Player p, Menu menu, Item clickedItem) { for (VarNumData data : dataList) { - String varName = Handlers.getPlaceholderHandler().replace(p, data.getName()); + String varName = AbstractMenusApi.get().providers().placeholders().replace(p, data.getName()); double value = data.getValue().getDouble(p, menu); Function func = num -> num + value; if (data.getPlayer() == null) { VariableManagerImpl.instance().modifyNumericGlobal(varName, func); } else { - String playerName = Handlers.getPlaceholderHandler().replace(p, data.getPlayer()); + String playerName = AbstractMenusApi.get().providers().placeholders().replace(p, data.getPlayer()); VariableManagerImpl.instance().modifyNumericPersonal(playerName, varName, func); } } diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarMul.java b/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarMul.java index 5b10c43..44f1c0b 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarMul.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarMul.java @@ -7,7 +7,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.variables.VarNumData; import ru.abstractmenus.variables.VariableManagerImpl; @@ -24,14 +24,14 @@ private ActionVarMul(List dataList) { public void activate(Player p, Menu menu, Item clickedItem) { for (VarNumData data : dataList) { - String varName = Handlers.getPlaceholderHandler().replace(p, data.getName()); + String varName = AbstractMenusApi.get().providers().placeholders().replace(p, data.getName()); double value = data.getValue().getDouble(p, menu); Function func = num -> num * value; if (data.getPlayer() == null) { VariableManagerImpl.instance().modifyNumericGlobal(varName, func); } else { - String playerName = Handlers.getPlaceholderHandler().replace(p, data.getPlayer()); + String playerName = AbstractMenusApi.get().providers().placeholders().replace(p, data.getPlayer()); VariableManagerImpl.instance().modifyNumericPersonal(playerName, varName, func); } } diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarRem.java b/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarRem.java index 3a635fd..d881d35 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarRem.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarRem.java @@ -7,7 +7,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.variables.VarData; import ru.abstractmenus.variables.VariableManagerImpl; @@ -23,12 +23,12 @@ private ActionVarRem(List dataList) { public void activate(Player p, Menu menu, Item clickedItem) { for (VarData data : dataList) { - String varName = Handlers.getPlaceholderHandler().replace(p, data.getName()); + String varName = AbstractMenusApi.get().providers().placeholders().replace(p, data.getName()); if (data.getPlayer() == null) { VariableManagerImpl.instance().deleteGlobal(varName); } else { - String playerName = Handlers.getPlaceholderHandler().replace(p, data.getPlayer()); + String playerName = AbstractMenusApi.get().providers().placeholders().replace(p, data.getPlayer()); VariableManagerImpl.instance().deletePersonal(playerName, varName); } } diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarSet.java b/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarSet.java index 2e3556d..6d61a24 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarSet.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/var/ActionVarSet.java @@ -8,7 +8,7 @@ import ru.abstractmenus.api.Action; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.util.TimeUtil; import ru.abstractmenus.variables.VarData; import ru.abstractmenus.variables.VariableManagerImpl; @@ -25,10 +25,10 @@ private ActionVarSet(List dataList) { public void activate(Player p, Menu menu, Item clickedItem) { for (VarData data : dataList) { - String varName = Handlers.getPlaceholderHandler().replace(p, data.getName()); - String varVal = Handlers.getPlaceholderHandler().replace(p, data.getValue()); + String varName = AbstractMenusApi.get().providers().placeholders().replace(p, data.getName()); + String varVal = AbstractMenusApi.get().providers().placeholders().replace(p, data.getValue()); - long time = TimeUtil.parseTime(Handlers.getPlaceholderHandler().replace(p, data.getTime())); + long time = TimeUtil.parseTime(AbstractMenusApi.get().providers().placeholders().replace(p, data.getTime())); boolean replace = data.isReplace().getBool(p, menu); Var var = VariableManagerImpl.instance().createBuilder() @@ -40,7 +40,7 @@ public void activate(Player p, Menu menu, Item clickedItem) { if (data.getPlayer() == null) { VariableManagerImpl.instance().saveGlobal(var, replace); } else { - String playerName = Handlers.getPlaceholderHandler().replace(p, data.getPlayer()); + String playerName = AbstractMenusApi.get().providers().placeholders().replace(p, data.getPlayer()); VariableManagerImpl.instance().savePersonal(playerName, var, replace); } } diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpDec.java b/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpDec.java index 597328f..8e52c7d 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpDec.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpDec.java @@ -2,7 +2,7 @@ import org.bukkit.entity.Player; import ru.abstractmenus.api.Action; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.hocon.api.ConfigNode; @@ -24,7 +24,7 @@ private ActionVarpDec(List dataList) { public void activate(Player p, Menu menu, Item clickedItem) { for (VarNumData data : dataList) { - String varName = Handlers.getPlaceholderHandler().replace(p, data.getName()); + String varName = AbstractMenusApi.get().providers().placeholders().replace(p, data.getName()); double value = data.getValue().getDouble(p, menu); Function func = num -> num - value; diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpDiv.java b/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpDiv.java index 0854ba6..869cf3a 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpDiv.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpDiv.java @@ -2,7 +2,7 @@ import org.bukkit.entity.Player; import ru.abstractmenus.api.Action; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.Logger; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; @@ -25,7 +25,7 @@ private ActionVarpDiv(List dataList) { public void activate(Player p, Menu menu, Item clickedItem) { for (VarNumData data : dataList) { - String varName = Handlers.getPlaceholderHandler().replace(p, data.getName()); + String varName = AbstractMenusApi.get().providers().placeholders().replace(p, data.getName()); double value = data.getValue().getDouble(p, menu); if (value == 0) { diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpInc.java b/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpInc.java index 6233d34..dbeb048 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpInc.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpInc.java @@ -2,7 +2,7 @@ import org.bukkit.entity.Player; import ru.abstractmenus.api.Action; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.hocon.api.ConfigNode; @@ -24,7 +24,7 @@ private ActionVarpInc(List dataList) { public void activate(Player p, Menu menu, Item clickedItem) { for (VarNumData data : dataList) { - String varName = Handlers.getPlaceholderHandler().replace(p, data.getName()); + String varName = AbstractMenusApi.get().providers().placeholders().replace(p, data.getName()); double value = data.getValue().getDouble(p, menu); Function func = num -> num + value; diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpMul.java b/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpMul.java index 10f3ebb..2ca0fbf 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpMul.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpMul.java @@ -2,7 +2,7 @@ import org.bukkit.entity.Player; import ru.abstractmenus.api.Action; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.hocon.api.ConfigNode; @@ -24,7 +24,7 @@ private ActionVarpMul(List dataList) { public void activate(Player p, Menu menu, Item clickedItem) { for (VarNumData data : dataList) { - String varName = Handlers.getPlaceholderHandler().replace(p, data.getName()); + String varName = AbstractMenusApi.get().providers().placeholders().replace(p, data.getName()); double value = data.getValue().getDouble(p, menu); Function func = num -> num * value; diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpRem.java b/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpRem.java index ef92b20..5256634 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpRem.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpRem.java @@ -2,7 +2,7 @@ import org.bukkit.entity.Player; import ru.abstractmenus.api.Action; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.hocon.api.ConfigNode; @@ -23,7 +23,7 @@ private ActionVarpRem(List dataList) { public void activate(Player p, Menu menu, Item clickedItem) { for (VarData data : dataList) { - String varName = Handlers.getPlaceholderHandler().replace(p, data.getName()); + String varName = AbstractMenusApi.get().providers().placeholders().replace(p, data.getName()); VariableManagerImpl.instance().deletePersonal(p.getName(), varName); } diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpSet.java b/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpSet.java index 2c46a30..1bb5fc2 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpSet.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/varp/ActionVarpSet.java @@ -2,7 +2,7 @@ import org.bukkit.entity.Player; import ru.abstractmenus.api.Action; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.variables.Var; @@ -25,10 +25,10 @@ private ActionVarpSet(List dataList) { public void activate(Player p, Menu menu, Item clickedItem) { for (VarData data : dataList) { - String varName = Handlers.getPlaceholderHandler().replace(p, data.getName()); - String varVal = Handlers.getPlaceholderHandler().replace(p, data.getValue()); + String varName = AbstractMenusApi.get().providers().placeholders().replace(p, data.getName()); + String varVal = AbstractMenusApi.get().providers().placeholders().replace(p, data.getValue()); - long time = TimeUtil.parseTime(Handlers.getPlaceholderHandler().replace(p, data.getTime())); + long time = TimeUtil.parseTime(AbstractMenusApi.get().providers().placeholders().replace(p, data.getTime())); boolean replace = data.isReplace().getBool(p, menu); Var var = VariableManagerImpl.instance().createBuilder() diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/wrappers/ActionPlayerScope.java b/plugin/src/main/java/ru/abstractmenus/data/actions/wrappers/ActionPlayerScope.java index 086fda0..d63fd85 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/wrappers/ActionPlayerScope.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/wrappers/ActionPlayerScope.java @@ -3,7 +3,7 @@ import org.bukkit.Bukkit; import org.bukkit.entity.Player; import ru.abstractmenus.api.Action; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.data.Actions; @@ -23,7 +23,7 @@ public ActionPlayerScope(String playerName, Actions actions) { @Override public void activate(Player player, Menu menu, Item clickedItem) { - String replacedName = Handlers.getPlaceholderHandler().replace(player, playerName); + String replacedName = AbstractMenusApi.get().providers().placeholders().replace(player, playerName); Player target = Bukkit.getPlayerExact(replacedName); if (target != null && target.isOnline()) { diff --git a/plugin/src/main/java/ru/abstractmenus/data/activators/OpenChat.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenChat.java index baccf22..31f0102 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/activators/OpenChat.java +++ b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenChat.java @@ -7,7 +7,7 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.player.AsyncPlayerChatEvent; import ru.abstractmenus.api.Activator; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.util.bukkit.BukkitTasks; import java.util.List; @@ -23,7 +23,7 @@ private OpenChat(List messages) { @EventHandler public void onChat(AsyncChatEvent event) { for (String str : messages) { - String replaced = Handlers.getPlaceholderHandler().replace(event.getPlayer(), str); + String replaced = AbstractMenusApi.get().providers().placeholders().replace(event.getPlayer(), str); if (event.signedMessage().message().equalsIgnoreCase(replaced)) { BukkitTasks.runTask(() -> openMenu(null, event.getPlayer())); diff --git a/plugin/src/main/java/ru/abstractmenus/data/activators/OpenChatContains.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenChatContains.java index e4f554b..24b5ed2 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/activators/OpenChatContains.java +++ b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenChatContains.java @@ -8,7 +8,7 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.player.AsyncPlayerChatEvent; import ru.abstractmenus.api.Activator; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.util.bukkit.BukkitTasks; import java.util.List; @@ -24,7 +24,7 @@ private OpenChatContains(List messages) { @EventHandler public void onChat(AsyncChatEvent event) { for (String str : messages) { - String msg = Handlers.getPlaceholderHandler().replace(event.getPlayer(), str); + String msg = AbstractMenusApi.get().providers().placeholders().replace(event.getPlayer(), str); if (event.signedMessage().message().contains(msg)) { BukkitTasks.runTask(() -> openMenu(null, event.getPlayer())); diff --git a/plugin/src/main/java/ru/abstractmenus/data/activators/OpenClickEntity.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenClickEntity.java index d229fc4..0aad579 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/activators/OpenClickEntity.java +++ b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenClickEntity.java @@ -5,7 +5,7 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.player.PlayerInteractEntityEvent; import ru.abstractmenus.api.Activator; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.ValueExtractor; import ru.abstractmenus.data.EntityData; import ru.abstractmenus.extractors.EntityExtractor; @@ -38,7 +38,7 @@ public void onEntityClick(PlayerInteractEntityEvent event) { } if (data.getName() != null) { - String expectedName = Handlers.getPlaceholderHandler().replace(player, data.getName()); + String expectedName = AbstractMenusApi.get().providers().placeholders().replace(player, data.getName()); if (!clickedEntity.getName().equalsIgnoreCase(expectedName)) { continue; } diff --git a/plugin/src/main/java/ru/abstractmenus/data/activators/OpenRegionEnter.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenRegionEnter.java index 680562b..34c10d2 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/activators/OpenRegionEnter.java +++ b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenRegionEnter.java @@ -7,7 +7,7 @@ import ru.abstractmenus.hocon.api.serialize.NodeSerializer; import org.bukkit.event.EventHandler; import ru.abstractmenus.api.Activator; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.extractors.RegionExtractor; import java.util.List; @@ -22,7 +22,7 @@ private OpenRegionEnter(List regions) { @EventHandler public void onRegionJoin(RegionEnterEvent event) { - List regions = Handlers.getPlaceholderHandler().replace(event.getPlayer(), this.regions); + List regions = AbstractMenusApi.get().providers().placeholders().replace(event.getPlayer(), this.regions); if (regions.contains(event.getRegion().getId())) { openMenu(event.getRegion(), event.getPlayer()); } diff --git a/plugin/src/main/java/ru/abstractmenus/data/activators/OpenRegionLeave.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenRegionLeave.java index 3c63818..e895719 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/activators/OpenRegionLeave.java +++ b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenRegionLeave.java @@ -7,7 +7,7 @@ import ru.abstractmenus.hocon.api.serialize.NodeSerializer; import org.bukkit.event.EventHandler; import ru.abstractmenus.api.Activator; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.extractors.RegionExtractor; import java.util.List; @@ -22,7 +22,7 @@ private OpenRegionLeave(List regions) { @EventHandler public void onRegionEnter(RegionLeaveEvent event) { - List regions = Handlers.getPlaceholderHandler().replace(event.getPlayer(), this.regions); + List regions = AbstractMenusApi.get().providers().placeholders().replace(event.getPlayer(), this.regions); if (regions.contains(event.getRegion().getId())) { openMenu(event.getRegion(), event.getPlayer()); diff --git a/plugin/src/main/java/ru/abstractmenus/data/activators/OpenShiftClickEntity.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenShiftClickEntity.java index c4327b6..092b4e0 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/activators/OpenShiftClickEntity.java +++ b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenShiftClickEntity.java @@ -5,7 +5,7 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.player.PlayerInteractEntityEvent; import ru.abstractmenus.api.Activator; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.ValueExtractor; import ru.abstractmenus.data.EntityData; import ru.abstractmenus.extractors.EntityExtractor; @@ -39,7 +39,7 @@ public void onEntityClick(PlayerInteractEntityEvent event) { } if (data.getName() == null) { - String name = Handlers.getPlaceholderHandler().replace(player, data.getName()); + String name = AbstractMenusApi.get().providers().placeholders().replace(player, data.getName()); if (clickedEntity.getName().equalsIgnoreCase(name)) { openMenu(clickedEntity, player); return; diff --git a/plugin/src/main/java/ru/abstractmenus/data/activators/OpenSign.java b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenSign.java index a47428c..6977a20 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/activators/OpenSign.java +++ b/plugin/src/main/java/ru/abstractmenus/data/activators/OpenSign.java @@ -8,7 +8,7 @@ import org.bukkit.event.block.Action; import org.bukkit.event.player.PlayerInteractEvent; import ru.abstractmenus.api.Activator; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.ValueExtractor; import ru.abstractmenus.api.text.Colors; import ru.abstractmenus.extractors.BlockExtractor; @@ -43,7 +43,7 @@ public void onTableClick(PlayerInteractEvent event) { String[] lines = sign.getLines(); int compareLines = Math.min(text.size(), lines.length); for (int i = 0; i < compareLines; i++) { - String line = Handlers.getPlaceholderHandler().replace(player, text.get(i)); + String line = AbstractMenusApi.get().providers().placeholders().replace(player, text.get(i)); if (!line.equalsIgnoreCase(lines[i])) return; } diff --git a/plugin/src/main/java/ru/abstractmenus/data/catalogs/SliceCatalog.java b/plugin/src/main/java/ru/abstractmenus/data/catalogs/SliceCatalog.java index b0ecf53..0720d63 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/catalogs/SliceCatalog.java +++ b/plugin/src/main/java/ru/abstractmenus/data/catalogs/SliceCatalog.java @@ -2,7 +2,7 @@ import org.bukkit.entity.Player; import ru.abstractmenus.api.Catalog; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.ValueExtractor; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.hocon.api.ConfigNode; @@ -36,7 +36,7 @@ public SliceCatalog(String value, String separator, boolean trim) { @Override public Collection snapshot(Player player, Menu menu) { - String replaced = Handlers.getPlaceholderHandler().replace(player, value); + String replaced = AbstractMenusApi.get().providers().placeholders().replace(player, value); String[] values = replaced.split(separator); return Arrays.stream(values) .filter(val -> !val.isEmpty()) diff --git a/plugin/src/main/java/ru/abstractmenus/data/comparator/LegacyValueComparator.java b/plugin/src/main/java/ru/abstractmenus/data/comparator/LegacyValueComparator.java index 8b2874e..74eec4d 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/comparator/LegacyValueComparator.java +++ b/plugin/src/main/java/ru/abstractmenus/data/comparator/LegacyValueComparator.java @@ -8,7 +8,7 @@ import ru.abstractmenus.hocon.api.serialize.NodeSerializer; import org.bukkit.entity.Player; import ru.abstractmenus.api.inventory.Menu; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import java.util.List; @@ -49,11 +49,11 @@ public Comparator(String param) { } public boolean compare(Player player, Menu menu){ - String param = Handlers.getPlaceholderHandler().replace(player, getParam()); + String param = AbstractMenusApi.get().providers().placeholders().replace(player, getParam()); if(equals != null){ for(String str : equals){ - String val = Handlers.getPlaceholderHandler().replace(player, str); + String val = AbstractMenusApi.get().providers().placeholders().replace(player, str); try{ if(Double.parseDouble(param) == Double.parseDouble(val)) return true; @@ -65,7 +65,7 @@ public boolean compare(Player player, Menu menu){ if(equalsIgnoreCase != null){ for(String str : equalsIgnoreCase){ - String val = Handlers.getPlaceholderHandler().replace(player, str); + String val = AbstractMenusApi.get().providers().placeholders().replace(player, str); if(param.equalsIgnoreCase(val)){ return true; } @@ -74,7 +74,7 @@ public boolean compare(Player player, Menu menu){ if(contains != null){ for(String str : contains){ - String val = Handlers.getPlaceholderHandler().replace(player, str); + String val = AbstractMenusApi.get().providers().placeholders().replace(player, str); if(param.toLowerCase().contains(val.toLowerCase())){ return true; } diff --git a/plugin/src/main/java/ru/abstractmenus/data/comparator/ModernValueComparator.java b/plugin/src/main/java/ru/abstractmenus/data/comparator/ModernValueComparator.java index 8b9a80c..65e472d 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/comparator/ModernValueComparator.java +++ b/plugin/src/main/java/ru/abstractmenus/data/comparator/ModernValueComparator.java @@ -5,7 +5,7 @@ import ru.abstractmenus.hocon.api.serialize.NodeSerializer; import org.bukkit.entity.Player; import ru.abstractmenus.api.inventory.Menu; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; public final class ModernValueComparator implements ValueComparator { @@ -18,7 +18,7 @@ private ModernValueComparator(String expression) { @Override public boolean compare(Player player, Menu menu) { - String replaced = Handlers.getPlaceholderHandler().replace(player, expression); + String replaced = AbstractMenusApi.get().providers().placeholders().replace(player, expression); String result = EVALUATOR.evaluate(replaced); return Boolean.parseBoolean(result); } diff --git a/plugin/src/main/java/ru/abstractmenus/data/properties/PropBookData.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropBookData.java index 9bf97bf..f2461d4 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/properties/PropBookData.java +++ b/plugin/src/main/java/ru/abstractmenus/data/properties/PropBookData.java @@ -10,7 +10,7 @@ import org.bukkit.inventory.meta.ItemMeta; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.ItemProperty; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import java.util.List; @@ -35,9 +35,9 @@ public boolean isApplyMeta() { @Override public void apply(ItemStack itemStack, ItemMeta meta, Player player, Menu menu) { if (meta instanceof BookMeta) { - String author = Handlers.getPlaceholderHandler().replace(player, data.getAuthor()); - String title = Handlers.getPlaceholderHandler().replace(player, data.getTitle()); - List pages = Handlers.getPlaceholderHandler().replace(player, data.getPages()); + String author = AbstractMenusApi.get().providers().placeholders().replace(player, data.getAuthor()); + String title = AbstractMenusApi.get().providers().placeholders().replace(player, data.getTitle()); + List pages = AbstractMenusApi.get().providers().placeholders().replace(player, data.getPages()); ((BookMeta) meta).setAuthor(author); ((BookMeta) meta).setTitle(title); diff --git a/plugin/src/main/java/ru/abstractmenus/data/properties/PropEquipItem.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropEquipItem.java index 8d23b23..44c4d31 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/properties/PropEquipItem.java +++ b/plugin/src/main/java/ru/abstractmenus/data/properties/PropEquipItem.java @@ -5,7 +5,7 @@ import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.Logger; import ru.abstractmenus.api.inventory.ItemProperty; import ru.abstractmenus.api.inventory.Menu; @@ -60,7 +60,7 @@ public void apply(ItemStack item, ItemMeta meta, Player player, Menu menu) { Player target = player; if (playerName != null) { - String replaced = Handlers.getPlaceholderHandler().replace(player, playerName); + String replaced = AbstractMenusApi.get().providers().placeholders().replace(player, playerName); Player found = Bukkit.getPlayerExact(replaced); if (found == null || !found.isOnline()) { diff --git a/plugin/src/main/java/ru/abstractmenus/data/properties/PropHDB.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropHDB.java index 14bcf12..9e7f37c 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/properties/PropHDB.java +++ b/plugin/src/main/java/ru/abstractmenus/data/properties/PropHDB.java @@ -10,7 +10,7 @@ import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.ItemProperty; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.util.bukkit.ItemUtil; import ru.abstractmenus.util.bukkit.Skulls; @@ -48,7 +48,7 @@ public boolean isApplyMeta() { @Override public void apply(ItemStack item, ItemMeta meta, Player player, Menu menu) { - String replaced = Handlers.getPlaceholderHandler().replace(player, id); + String replaced = AbstractMenusApi.get().providers().placeholders().replace(player, id); ItemStack headItem = api().getItemHead(replaced); if (headItem == null) diff --git a/plugin/src/main/java/ru/abstractmenus/data/properties/PropItemsAdder.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropItemsAdder.java index 4949967..7b2ae92 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/properties/PropItemsAdder.java +++ b/plugin/src/main/java/ru/abstractmenus/data/properties/PropItemsAdder.java @@ -10,7 +10,7 @@ import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.ItemProperty; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.util.bukkit.ItemUtil; public class PropItemsAdder implements ItemProperty { @@ -33,7 +33,7 @@ public boolean isApplyMeta() { @Override public void apply(ItemStack item, ItemMeta meta, Player player, Menu menu) { - String id = Handlers.getPlaceholderHandler().replace(player, this.id); + String id = AbstractMenusApi.get().providers().placeholders().replace(player, this.id); CustomStack stack = CustomStack.getInstance(id); if (stack == null) diff --git a/plugin/src/main/java/ru/abstractmenus/data/properties/PropLore.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropLore.java index 62a9528..c21760f 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/properties/PropLore.java +++ b/plugin/src/main/java/ru/abstractmenus/data/properties/PropLore.java @@ -9,7 +9,7 @@ import org.bukkit.inventory.meta.ItemMeta; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.ItemProperty; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.text.Colors; import ru.abstractmenus.util.MiniMessageUtil; @@ -68,7 +68,7 @@ public void apply(ItemStack itemStack, ItemMeta meta, Player player, Menu menu) meta.setLore(preFormatted); return; } - List replaced = Handlers.getPlaceholderHandler().replace(player, lore); + List replaced = AbstractMenusApi.get().providers().placeholders().replace(player, lore); meta.setLore(MiniMessageUtil.parseToLegacy(replaced)); } diff --git a/plugin/src/main/java/ru/abstractmenus/data/properties/PropLoreLight.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropLoreLight.java index 73984b3..663f650 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/properties/PropLoreLight.java +++ b/plugin/src/main/java/ru/abstractmenus/data/properties/PropLoreLight.java @@ -3,7 +3,7 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.inventory.ItemProperty; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.text.Colors; @@ -33,7 +33,7 @@ public boolean isApplyMeta() { @Override public void apply(ItemStack itemStack, ItemMeta meta, Player player, Menu menu) { - List replaced = Handlers.getPlaceholderHandler().replace(player, lore); + List replaced = AbstractMenusApi.get().providers().placeholders().replace(player, lore); meta.setLore(replaced); } diff --git a/plugin/src/main/java/ru/abstractmenus/data/properties/PropMmoItem.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropMmoItem.java index 68999c6..cc54614 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/properties/PropMmoItem.java +++ b/plugin/src/main/java/ru/abstractmenus/data/properties/PropMmoItem.java @@ -10,7 +10,7 @@ import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.ItemProperty; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.Logger; import ru.abstractmenus.util.bukkit.ItemUtil; @@ -36,7 +36,7 @@ public boolean isApplyMeta() { @Override public void apply(ItemStack item, ItemMeta meta, Player player, Menu menu) { - String[] arr = Handlers.getPlaceholderHandler().replace(player, id).split(":"); + String[] arr = AbstractMenusApi.get().providers().placeholders().replace(player, id).split(":"); if(arr.length >= 2) { ItemStack mmoItem = MMOItems.plugin.getItem(MMOItems.plugin.getTypes().get(arr[0]), arr[1]); diff --git a/plugin/src/main/java/ru/abstractmenus/data/properties/PropName.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropName.java index 47c0af7..bda8de0 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/properties/PropName.java +++ b/plugin/src/main/java/ru/abstractmenus/data/properties/PropName.java @@ -9,7 +9,7 @@ import org.bukkit.inventory.meta.ItemMeta; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.ItemProperty; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.text.Colors; import ru.abstractmenus.util.MiniMessageUtil; @@ -58,7 +58,7 @@ public void apply(ItemStack itemStack, ItemMeta meta, Player player, Menu menu) meta.setDisplayName(preFormatted); return; } - String replaced = Handlers.getPlaceholderHandler().replace(player, name); + String replaced = AbstractMenusApi.get().providers().placeholders().replace(player, name); meta.setDisplayName(MiniMessageUtil.parseToLegacy(replaced)); } diff --git a/plugin/src/main/java/ru/abstractmenus/data/properties/PropNameLight.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropNameLight.java index af2cecb..85100f6 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/properties/PropNameLight.java +++ b/plugin/src/main/java/ru/abstractmenus/data/properties/PropNameLight.java @@ -3,7 +3,7 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.inventory.ItemProperty; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.text.Colors; @@ -31,7 +31,7 @@ public boolean isApplyMeta() { @Override public void apply(ItemStack itemStack, ItemMeta meta, Player player, Menu menu) { - String replaced = Handlers.getPlaceholderHandler().replace(player, name); + String replaced = AbstractMenusApi.get().providers().placeholders().replace(player, name); meta.setDisplayName(replaced); } diff --git a/plugin/src/main/java/ru/abstractmenus/data/properties/PropOraxen.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropOraxen.java index 91823c7..8ef134b 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/properties/PropOraxen.java +++ b/plugin/src/main/java/ru/abstractmenus/data/properties/PropOraxen.java @@ -10,7 +10,7 @@ import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.ItemProperty; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.util.bukkit.ItemUtil; import java.lang.invoke.MethodHandle; @@ -62,7 +62,7 @@ public void apply(ItemStack item, ItemMeta meta, Player player, Menu menu) { } private ItemStack getItem(Player player) { - String id = Handlers.getPlaceholderHandler().replace(player, this.id); + String id = AbstractMenusApi.get().providers().placeholders().replace(player, this.id); try { Object builder = getItemByIdMethod.invoke(id); if (builder == null) diff --git a/plugin/src/main/java/ru/abstractmenus/data/properties/PropSerialized.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropSerialized.java index 50ae0ff..77e4ca3 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/properties/PropSerialized.java +++ b/plugin/src/main/java/ru/abstractmenus/data/properties/PropSerialized.java @@ -4,7 +4,7 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.Logger; import ru.abstractmenus.api.inventory.ItemProperty; import ru.abstractmenus.api.inventory.Menu; @@ -33,7 +33,7 @@ public boolean isApplyMeta() { @Override public void apply(ItemStack item, ItemMeta meta, Player player, Menu menu) { - String base64 = Handlers.getPlaceholderHandler().replace(player, value); + String base64 = AbstractMenusApi.get().providers().placeholders().replace(player, value); ItemStack deserialized = ItemUtil.decodeStack(base64); if (deserialized == null) { diff --git a/plugin/src/main/java/ru/abstractmenus/data/properties/PropSkullOwner.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropSkullOwner.java index b8e1c81..5ba3521 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/properties/PropSkullOwner.java +++ b/plugin/src/main/java/ru/abstractmenus/data/properties/PropSkullOwner.java @@ -11,7 +11,7 @@ import ru.abstractmenus.util.bukkit.ItemUtil; import ru.abstractmenus.api.inventory.Menu; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.util.bukkit.Skulls; public class PropSkullOwner implements ItemProperty { @@ -34,7 +34,7 @@ public boolean isApplyMeta() { @Override public void apply(ItemStack item, ItemMeta meta, Player player, Menu menu) { - String replaced = Handlers.getPlaceholderHandler().replace(player, owner); + String replaced = AbstractMenusApi.get().providers().placeholders().replace(player, owner); ItemStack skullItem = Skulls.getPlayerSkull(replaced); if (skullItem == null) { diff --git a/plugin/src/main/java/ru/abstractmenus/data/properties/PropTexture.java b/plugin/src/main/java/ru/abstractmenus/data/properties/PropTexture.java index 76c141c..7044b8a 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/properties/PropTexture.java +++ b/plugin/src/main/java/ru/abstractmenus/data/properties/PropTexture.java @@ -14,7 +14,7 @@ import ru.abstractmenus.api.inventory.ItemProperty; import ru.abstractmenus.util.bukkit.ItemUtil; import ru.abstractmenus.util.bukkit.Skulls; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import java.net.HttpURLConnection; import java.nio.charset.StandardCharsets; @@ -43,7 +43,7 @@ public boolean isApplyMeta() { @Override public void apply(ItemStack item, ItemMeta meta, Player player, Menu menu) { - String replaced = Handlers.getPlaceholderHandler().replace(player, texture); + String replaced = AbstractMenusApi.get().providers().placeholders().replace(player, texture); ItemStack skullItem = Skulls.getCustomSkull(fetchTextureUrl(replaced)); if (skullItem != null) { diff --git a/plugin/src/main/java/ru/abstractmenus/data/rules/RuleBungeeIsOnline.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleBungeeIsOnline.java index 413958f..469fdae 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/rules/RuleBungeeIsOnline.java +++ b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleBungeeIsOnline.java @@ -7,7 +7,7 @@ import ru.abstractmenus.api.Rule; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.services.BungeeManager; public class RuleBungeeIsOnline implements Rule { @@ -20,7 +20,7 @@ private RuleBungeeIsOnline(String server){ @Override public boolean check(Player player, Menu menu, Item clickedItem) { - return BungeeManager.instance().isOnline(Handlers.getPlaceholderHandler().replace(player, server)); + return BungeeManager.instance().isOnline(AbstractMenusApi.get().providers().placeholders().replace(player, server)); } public static class Serializer implements NodeSerializer { diff --git a/plugin/src/main/java/ru/abstractmenus/data/rules/RuleBungeeOnline.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleBungeeOnline.java index 7212fad..60be6fd 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/rules/RuleBungeeOnline.java +++ b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleBungeeOnline.java @@ -8,7 +8,7 @@ import ru.abstractmenus.api.Rule; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.services.BungeeManager; import ru.abstractmenus.datatype.TypeInt; @@ -24,7 +24,7 @@ private RuleBungeeOnline(String server, TypeInt online){ @Override public boolean check(Player player, Menu menu, Item clickedItem) { - return BungeeManager.instance().getOnline(Handlers.getPlaceholderHandler().replace(player, server)) >= online.getInt(player, menu); + return BungeeManager.instance().getOnline(AbstractMenusApi.get().providers().placeholders().replace(player, server)) >= online.getInt(player, menu); } public static class Serializer implements NodeSerializer { diff --git a/plugin/src/main/java/ru/abstractmenus/data/rules/RuleExistVar.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleExistVar.java index 01056d8..d368571 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/rules/RuleExistVar.java +++ b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleExistVar.java @@ -7,7 +7,7 @@ import ru.abstractmenus.api.Rule; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.variables.VariableManagerImpl; public class RuleExistVar implements Rule { @@ -22,12 +22,12 @@ private RuleExistVar(String player, String name){ @Override public boolean check(Player p, Menu menu, Item clickedItem) { - String varName = Handlers.getPlaceholderHandler().replace(p, this.name); + String varName = AbstractMenusApi.get().providers().placeholders().replace(p, this.name); if(this.player == null) { return VariableManagerImpl.instance().getGlobal(varName) != null; } else { - String varPlayer = Handlers.getPlaceholderHandler().replace(p, this.player); + String varPlayer = AbstractMenusApi.get().providers().placeholders().replace(p, this.player); return VariableManagerImpl.instance().getPersonal(varPlayer, varName) != null; } } diff --git a/plugin/src/main/java/ru/abstractmenus/data/rules/RuleExistVarp.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleExistVarp.java index 169d0ad..dc44f77 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/rules/RuleExistVarp.java +++ b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleExistVarp.java @@ -1,7 +1,7 @@ package ru.abstractmenus.data.rules; import org.bukkit.entity.Player; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.Rule; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; @@ -20,7 +20,7 @@ private RuleExistVarp(String name){ @Override public boolean check(Player p, Menu menu, Item clickedItem) { - String varName = Handlers.getPlaceholderHandler().replace(p, this.name); + String varName = AbstractMenusApi.get().providers().placeholders().replace(p, this.name); return VariableManagerImpl.instance().getPersonal(p.getName(), varName) != null; } diff --git a/plugin/src/main/java/ru/abstractmenus/data/rules/RuleGroup.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleGroup.java index ce13402..95de37f 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/rules/RuleGroup.java +++ b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleGroup.java @@ -7,7 +7,7 @@ import ru.abstractmenus.api.Rule; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import java.util.List; @@ -22,8 +22,8 @@ private RuleGroup(List groups) { @Override public boolean check(Player player, Menu menu, Item clickedItem) { for (String group : groups) { - String replaced = Handlers.getPlaceholderHandler().replace(player, group); - if (!Handlers.getPermissionsHandler().hasGroup(player, replaced)) return false; + String replaced = AbstractMenusApi.get().providers().placeholders().replace(player, group); + if (!AbstractMenusApi.get().providers().permissions().hasGroup(player, replaced)) return false; } return true; } diff --git a/plugin/src/main/java/ru/abstractmenus/data/rules/RuleJS.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleJS.java index 4086255..f9d41de 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/rules/RuleJS.java +++ b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleJS.java @@ -6,7 +6,7 @@ import ru.abstractmenus.api.Rule; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.Logger; import javax.script.*; @@ -41,7 +41,7 @@ private RuleJS(String js){ @Override public boolean check(Player player, Menu menu, Item clickedItem) { try{ - Object result = ENGINE.eval(Handlers.getPlaceholderHandler().replace(player, js), bindings); + Object result = ENGINE.eval(AbstractMenusApi.get().providers().placeholders().replace(player, js), bindings); return result.toString().equals("true"); } catch (ScriptException e){ Logger.severe("Cannot execute JavaScript code: " + e.getMessage()); diff --git a/plugin/src/main/java/ru/abstractmenus/data/rules/RuleLevel.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleLevel.java index dba7b2b..86dcd06 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/rules/RuleLevel.java +++ b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleLevel.java @@ -7,7 +7,7 @@ import ru.abstractmenus.api.Rule; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.datatype.TypeInt; public class RuleLevel implements Rule { @@ -20,7 +20,7 @@ private RuleLevel(TypeInt level){ @Override public boolean check(Player player, Menu menu, Item clickedItem) { - return Handlers.getLevelHandler().getLevel(player) >= level.getInt(player, menu); + return AbstractMenusApi.get().providers().levels().getLevel(player) >= level.getInt(player, menu); } public static class Serializer implements NodeSerializer { diff --git a/plugin/src/main/java/ru/abstractmenus/data/rules/RuleMoney.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleMoney.java index ca73a5d..e9be945 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/rules/RuleMoney.java +++ b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleMoney.java @@ -7,7 +7,7 @@ import ru.abstractmenus.api.Rule; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.datatype.TypeDouble; public class RuleMoney implements Rule { @@ -20,8 +20,8 @@ private RuleMoney(TypeDouble money){ @Override public boolean check(Player player, Menu menu, Item clickedItem) { - if(Handlers.getEconomyHandler() != null){ - return Handlers.getEconomyHandler().hasBalance(player, money.getDouble(player, menu)); + if(AbstractMenusApi.get().providers().economy() != null){ + return AbstractMenusApi.get().providers().economy().hasBalance(player, money.getDouble(player, menu)); } return false; } diff --git a/plugin/src/main/java/ru/abstractmenus/data/rules/RulePermission.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RulePermission.java index ed619d8..b843268 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/rules/RulePermission.java +++ b/plugin/src/main/java/ru/abstractmenus/data/rules/RulePermission.java @@ -7,7 +7,7 @@ import ru.abstractmenus.api.Rule; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import java.util.List; @@ -22,8 +22,8 @@ private RulePermission(List permission){ @Override public boolean check(Player player, Menu menu, Item clickedItem) { for (String perm : permission){ - String replaced = Handlers.getPlaceholderHandler().replace(player, perm); - if(!Handlers.getPermissionsHandler().hasPermission(player, replaced)) return false; + String replaced = AbstractMenusApi.get().providers().placeholders().replace(player, perm); + if(!AbstractMenusApi.get().providers().permissions().hasPermission(player, replaced)) return false; } return true; } diff --git a/plugin/src/main/java/ru/abstractmenus/data/rules/RulePlayerIsOnline.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RulePlayerIsOnline.java index 8d513e5..939ce1c 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/rules/RulePlayerIsOnline.java +++ b/plugin/src/main/java/ru/abstractmenus/data/rules/RulePlayerIsOnline.java @@ -2,7 +2,7 @@ import org.bukkit.Bukkit; import org.bukkit.entity.Player; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.Rule; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; @@ -20,7 +20,7 @@ private RulePlayerIsOnline(String playerName){ @Override public boolean check(Player player, Menu menu, Item clickedItem) { - String replaced = Handlers.getPlaceholderHandler().replace(player, playerName); + String replaced = AbstractMenusApi.get().providers().placeholders().replace(player, playerName); Player foundPlayer = Bukkit.getPlayerExact(replaced); return foundPlayer != null && foundPlayer.isOnline(); diff --git a/plugin/src/main/java/ru/abstractmenus/data/rules/RuleRegion.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleRegion.java index d94f5ab..5c47cd7 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/rules/RuleRegion.java +++ b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleRegion.java @@ -8,7 +8,7 @@ import ru.abstractmenus.api.Rule; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.util.RegionUtils; import java.util.List; @@ -24,7 +24,7 @@ private RuleRegion(List regions){ @Override public boolean check(Player player, Menu menu, Item clickedItem) { for (String reg : regions){ - String replaced = Handlers.getPlaceholderHandler().replace(player, reg); + String replaced = AbstractMenusApi.get().providers().placeholders().replace(player, reg); ProtectedRegion region = RegionUtils.getRegion(player.getWorld(), replaced); if(region != null && region.contains( diff --git a/plugin/src/main/java/ru/abstractmenus/data/rules/RuleXp.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleXp.java index c070494..f9784d0 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/rules/RuleXp.java +++ b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleXp.java @@ -7,7 +7,7 @@ import ru.abstractmenus.api.Rule; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.datatype.TypeFloat; public class RuleXp implements Rule { @@ -20,7 +20,7 @@ private RuleXp(TypeFloat xp) { @Override public boolean check(Player player, Menu menu, Item clickedItem) { - return Handlers.getLevelHandler().getXp(player) >= xp.getFloat(player, menu); + return AbstractMenusApi.get().providers().levels().getXp(player) >= xp.getFloat(player, menu); } public static class Serializer implements NodeSerializer { diff --git a/plugin/src/main/java/ru/abstractmenus/data/rules/logical/RulePlayerScope.java b/plugin/src/main/java/ru/abstractmenus/data/rules/logical/RulePlayerScope.java index daf3067..7578af7 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/rules/logical/RulePlayerScope.java +++ b/plugin/src/main/java/ru/abstractmenus/data/rules/logical/RulePlayerScope.java @@ -2,7 +2,7 @@ import org.bukkit.Bukkit; import org.bukkit.entity.Player; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.Rule; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.inventory.Menu; @@ -22,7 +22,7 @@ public RulePlayerScope(String playerName, Rule rules) { @Override public boolean check(Player player, Menu menu, Item clickedItem) { - String replacedName = Handlers.getPlaceholderHandler().replace(player, playerName); + String replacedName = AbstractMenusApi.get().providers().placeholders().replace(player, playerName); Player target = Bukkit.getPlayerExact(replacedName); if (target != null && target.isOnline()) { diff --git a/plugin/src/main/java/ru/abstractmenus/datatype/DataType.java b/plugin/src/main/java/ru/abstractmenus/datatype/DataType.java index e304cf4..3fd668d 100644 --- a/plugin/src/main/java/ru/abstractmenus/datatype/DataType.java +++ b/plugin/src/main/java/ru/abstractmenus/datatype/DataType.java @@ -3,7 +3,7 @@ import lombok.Getter; import org.bukkit.entity.Player; import ru.abstractmenus.api.inventory.Menu; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; public abstract class DataType implements Cloneable { @@ -15,7 +15,7 @@ public abstract class DataType implements Cloneable { } public String replaceFor(Player player, Menu menu) { - return Handlers.getPlaceholderHandler().replace(player, value); + return AbstractMenusApi.get().providers().placeholders().replace(player, value); } public static boolean hasPlaceholder(String string) { diff --git a/plugin/src/main/java/ru/abstractmenus/extractors/PlayerExtractor.java b/plugin/src/main/java/ru/abstractmenus/extractors/PlayerExtractor.java index d7829ca..4f21728 100644 --- a/plugin/src/main/java/ru/abstractmenus/extractors/PlayerExtractor.java +++ b/plugin/src/main/java/ru/abstractmenus/extractors/PlayerExtractor.java @@ -2,7 +2,7 @@ import org.apache.commons.lang3.StringUtils; import org.bukkit.entity.Player; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.ValueExtractor; public class PlayerExtractor implements ValueExtractor { @@ -12,7 +12,7 @@ public class PlayerExtractor implements ValueExtractor { @Override public String extract(Object obj, String placeholder) { return (obj instanceof Player player && player.isOnline()) - ? Handlers.getPlaceholderHandler().replacePlaceholder(player, placeholder) + ? AbstractMenusApi.get().providers().placeholders().replacePlaceholder(player, placeholder) : StringUtils.EMPTY; } } diff --git a/plugin/src/main/java/ru/abstractmenus/menu/AbstractMenu.java b/plugin/src/main/java/ru/abstractmenus/menu/AbstractMenu.java index c73bbe3..5d59ed5 100644 --- a/plugin/src/main/java/ru/abstractmenus/menu/AbstractMenu.java +++ b/plugin/src/main/java/ru/abstractmenus/menu/AbstractMenu.java @@ -11,7 +11,7 @@ import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; -import ru.abstractmenus.api.Handlers; +import ru.abstractmenus.api.AbstractMenusApi; import ru.abstractmenus.api.Rule; import ru.abstractmenus.datatype.TypeSlot; import ru.abstractmenus.menu.item.MenuItem; @@ -323,7 +323,7 @@ protected int getFreeSlot() { } protected void createInventory(Player player, InventoryHolder holder) { - String title = Handlers.getPlaceholderHandler().replace(player, this.title); + String title = AbstractMenusApi.get().providers().placeholders().replace(player, this.title); title = MiniMessageUtil.parseToLegacy(title); if (this.type != null) { diff --git a/plugin/src/test/java/ru/abstractmenus/data/actions/TestActionCommandBehavior.java b/plugin/src/test/java/ru/abstractmenus/data/actions/TestActionCommandBehavior.java index bba58e7..ff63a97 100644 --- a/plugin/src/test/java/ru/abstractmenus/data/actions/TestActionCommandBehavior.java +++ b/plugin/src/test/java/ru/abstractmenus/data/actions/TestActionCommandBehavior.java @@ -5,8 +5,8 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; -import ru.abstractmenus.api.Handlers; import ru.abstractmenus.api.handler.PlaceholderHandler; +import ru.abstractmenus.testsupport.ApiTestSupport; import ru.abstractmenus.hocon.api.ConfigNode; import ru.abstractmenus.hocon.api.ConfigurationLoader; import ru.abstractmenus.hocon.api.source.ConfigSources; @@ -26,12 +26,12 @@ */ class TestActionCommandBehavior { - private static PlaceholderHandler previousHandler; + private static ApiTestSupport apiSupport; @BeforeAll static void installIdentityHandler() { - previousHandler = Handlers.getPlaceholderHandler(); - Handlers.setPlaceholderHandler(new PlaceholderHandler() { + apiSupport = ApiTestSupport.install(); + apiSupport.installPlaceholderHandler(new PlaceholderHandler() { @Override public String replacePlaceholder(Player p, String s) { return s; } @Override public String replace(Player p, String s) { return s; } @Override public List replace(Player p, List l) { return l; } @@ -41,7 +41,7 @@ static void installIdentityHandler() { @AfterAll static void restore() { - Handlers.setPlaceholderHandler(previousHandler); + apiSupport.close(); } @Test @@ -84,13 +84,19 @@ void placeholderReplacementRunsExactlyOnce() throws Exception { // The old impl called replace(...) twice ("replace(replace(...))"), the // new one replaces once. Install a counting handler to enforce. int[] callCount = {0}; - PlaceholderHandler saved = Handlers.getPlaceholderHandler(); - Handlers.setPlaceholderHandler(new PlaceholderHandler() { + // Register a higher-priority counting handler that preempts the identity + // one installed in @BeforeAll; unregister it in the finally block so the + // identity handler remains active for the other tests. + ru.abstractmenus.api.MenuExtension scratchOwner = new ru.abstractmenus.api.MenuExtension() { + @Override public String name() { return "countingTestOwner"; } + @Override public void onEnable(ru.abstractmenus.api.AbstractMenusApi api) {} + }; + apiSupport.providers().registerPlaceholders("counting", new PlaceholderHandler() { @Override public String replacePlaceholder(Player p, String s) { return s; } @Override public String replace(Player p, String s) { callCount[0]++; return s; } @Override public List replace(Player p, List l) { return l; } @Override public void registerAll() {} - }); + }, 200, scratchOwner); try { ActionCommand action = buildAction("{ player = \"give %player_name% gold\" }"); Player player = mock(Player.class); @@ -100,7 +106,7 @@ void placeholderReplacementRunsExactlyOnce() throws Exception { org.junit.jupiter.api.Assertions.assertEquals(1, callCount[0], "PlaceholderHandler.replace must be called exactly once per command"); } finally { - Handlers.setPlaceholderHandler(saved); + apiSupport.providers().unregisterAll(scratchOwner); } } diff --git a/plugin/src/test/java/ru/abstractmenus/data/properties/TestPropNameLorePrecompute.java b/plugin/src/test/java/ru/abstractmenus/data/properties/TestPropNameLorePrecompute.java index b63656a..6c9e58a 100644 --- a/plugin/src/test/java/ru/abstractmenus/data/properties/TestPropNameLorePrecompute.java +++ b/plugin/src/test/java/ru/abstractmenus/data/properties/TestPropNameLorePrecompute.java @@ -7,12 +7,12 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; -import ru.abstractmenus.api.Handlers; import ru.abstractmenus.api.handler.PlaceholderHandler; import ru.abstractmenus.api.text.Colors; import ru.abstractmenus.hocon.api.ConfigNode; import ru.abstractmenus.hocon.api.ConfigurationLoader; import ru.abstractmenus.hocon.api.source.ConfigSources; +import ru.abstractmenus.testsupport.ApiTestSupport; import ru.abstractmenus.util.MiniMessageUtil; import java.io.ByteArrayInputStream; @@ -37,15 +37,15 @@ */ class TestPropNameLorePrecompute { - private static PlaceholderHandler previousHandler; + private static ApiTestSupport apiSupport; private static int replaceCallCount; @BeforeAll static void setUp() { MiniMessageUtil.init(false); Colors.init(false); // pass-through; avoids LegacyComponentSerializer init. - previousHandler = Handlers.getPlaceholderHandler(); - Handlers.setPlaceholderHandler(new PlaceholderHandler() { + apiSupport = ApiTestSupport.install(); + apiSupport.installPlaceholderHandler(new PlaceholderHandler() { @Override public String replacePlaceholder(Player p, String s) { return s; } @Override public String replace(Player p, String s) { replaceCallCount++; return s; } @Override public List replace(Player p, List l) { replaceCallCount++; return l; } @@ -55,7 +55,7 @@ static void setUp() { @AfterAll static void tearDown() { - Handlers.setPlaceholderHandler(previousHandler); + apiSupport.close(); // Intentionally leave MiniMessageUtil in inactive mode: re-init(true) // would trigger LegacyComponentSerializer.builder(), which collides // on the test classpath (two Adventure Provider impls). No other test diff --git a/plugin/src/test/java/ru/abstractmenus/datatype/TestCompoundDataTypes.java b/plugin/src/test/java/ru/abstractmenus/datatype/TestCompoundDataTypes.java index aa727a7..8c9324f 100644 --- a/plugin/src/test/java/ru/abstractmenus/datatype/TestCompoundDataTypes.java +++ b/plugin/src/test/java/ru/abstractmenus/datatype/TestCompoundDataTypes.java @@ -6,8 +6,8 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import ru.abstractmenus.api.Handlers; import ru.abstractmenus.api.handler.PlaceholderHandler; +import ru.abstractmenus.testsupport.ApiTestSupport; import ru.abstractmenus.api.inventory.Slot; import ru.abstractmenus.api.inventory.slot.SlotIndex; import ru.abstractmenus.api.inventory.slot.SlotPos; @@ -33,13 +33,13 @@ */ class TestCompoundDataTypes { - private static PlaceholderHandler previousHandler; + private static ApiTestSupport apiSupport; @BeforeAll static void installIdentityPlaceholderHandler() { - previousHandler = Handlers.getPlaceholderHandler(); + apiSupport = ApiTestSupport.install(); // Identity handler — tests in this file don't exercise actual replacement. - Handlers.setPlaceholderHandler(new PlaceholderHandler() { + apiSupport.installPlaceholderHandler(new PlaceholderHandler() { @Override public String replacePlaceholder(org.bukkit.entity.Player p, String s) { return s; } @Override public String replace(org.bukkit.entity.Player p, String s) { return s; } @Override public List replace(org.bukkit.entity.Player p, List l) { return l; } @@ -49,7 +49,7 @@ static void installIdentityPlaceholderHandler() { @AfterAll static void restorePlaceholderHandler() { - Handlers.setPlaceholderHandler(previousHandler); + apiSupport.close(); } private static ConfigNode loadValueNode(String hocon) throws Exception { diff --git a/plugin/src/test/java/ru/abstractmenus/datatype/TestPrimitiveDataTypes.java b/plugin/src/test/java/ru/abstractmenus/datatype/TestPrimitiveDataTypes.java index f10fd54..2c4ff3c 100644 --- a/plugin/src/test/java/ru/abstractmenus/datatype/TestPrimitiveDataTypes.java +++ b/plugin/src/test/java/ru/abstractmenus/datatype/TestPrimitiveDataTypes.java @@ -4,8 +4,8 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import ru.abstractmenus.api.Handlers; import ru.abstractmenus.api.handler.PlaceholderHandler; +import ru.abstractmenus.testsupport.ApiTestSupport; import ru.abstractmenus.hocon.api.ConfigNode; import ru.abstractmenus.hocon.api.ConfigurationLoader; import ru.abstractmenus.hocon.api.serialize.NodeSerializeException; @@ -26,14 +26,14 @@ */ class TestPrimitiveDataTypes { - private static PlaceholderHandler previousHandler; + private static ApiTestSupport apiSupport; private static final Map placeholderMap = new HashMap<>(); @BeforeAll static void setUpPlaceholders() { - previousHandler = Handlers.getPlaceholderHandler(); + apiSupport = ApiTestSupport.install(); // Minimal PlaceholderHandler that echoes lookups from the map; unresolved → returned as-is. - Handlers.setPlaceholderHandler(new PlaceholderHandler() { + apiSupport.installPlaceholderHandler(new PlaceholderHandler() { @Override public String replacePlaceholder(org.bukkit.entity.Player p, String s) { return replace(p, s); @@ -61,7 +61,7 @@ public void registerAll() {} @AfterAll static void tearDown() { - Handlers.setPlaceholderHandler(previousHandler); + apiSupport.close(); placeholderMap.clear(); } diff --git a/plugin/src/test/java/ru/abstractmenus/testsupport/ApiTestSupport.java b/plugin/src/test/java/ru/abstractmenus/testsupport/ApiTestSupport.java new file mode 100644 index 0000000..1168305 --- /dev/null +++ b/plugin/src/test/java/ru/abstractmenus/testsupport/ApiTestSupport.java @@ -0,0 +1,121 @@ +package ru.abstractmenus.testsupport; + +import org.bukkit.Bukkit; +import org.bukkit.plugin.ServicesManager; +import org.mockito.MockedStatic; +import ru.abstractmenus.api.AbstractMenusApi; +import ru.abstractmenus.api.Action; +import ru.abstractmenus.api.Activator; +import ru.abstractmenus.api.Catalog; +import ru.abstractmenus.api.MenuExtension; +import ru.abstractmenus.api.ProviderRegistry; +import ru.abstractmenus.api.ProviderRegistryImpl; +import ru.abstractmenus.api.Rule; +import ru.abstractmenus.api.TypeRegistry; +import ru.abstractmenus.api.TypeRegistryImpl; +import ru.abstractmenus.api.handler.PlaceholderHandler; +import ru.abstractmenus.api.inventory.ItemProperty; +import ru.abstractmenus.api.inventory.Menu; +import ru.abstractmenus.api.variables.VariableManager; +import ru.abstractmenus.hocon.api.serialize.NodeSerializers; + +import java.util.Optional; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +/** + * Helper for unit tests that exercise production code calling + * {@code AbstractMenusApi.get().providers().*()}. + * + *

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

Typical usage: + *

{@code
+ * private static ApiTestSupport support;
+ *
+ * @BeforeAll
+ * static void setUp() {
+ *     support = ApiTestSupport.install();
+ *     support.providers().registerPlaceholders("test", myHandler, 100, support.owner());
+ * }
+ *
+ * @AfterAll
+ * static void tearDown() { support.close(); }
+ * }
+ */ +public final class ApiTestSupport implements AutoCloseable { + + private final MockedStatic bukkitMock; + private final StubApi api; + private final TestOwner owner; + + private ApiTestSupport(MockedStatic bukkitMock, StubApi api, TestOwner owner) { + this.bukkitMock = bukkitMock; + this.api = api; + this.owner = owner; + } + + public static ApiTestSupport install() { + StubApi api = new StubApi(); + + ServicesManager servicesManager = mock(ServicesManager.class); + when(servicesManager.load(AbstractMenusApi.class)).thenReturn(api); + + MockedStatic bukkitMock = mockStatic(Bukkit.class); + bukkitMock.when(Bukkit::getServicesManager).thenReturn(servicesManager); + + return new ApiTestSupport(bukkitMock, api, new TestOwner()); + } + + public ProviderRegistry providers() { return api.providers(); } + public MenuExtension owner() { return owner; } + public AbstractMenusApi api() { return api; } + + /** Shortcut: register {@code handler} as the test placeholder provider. */ + public void installPlaceholderHandler(PlaceholderHandler handler) { + api.providers().registerPlaceholders("test", handler, 100, owner); + } + + @Override + public void close() { + bukkitMock.close(); + } + + // ------------------------------------------------------------------- + + private static final class TestOwner implements MenuExtension { + @Override public String name() { return "ApiTestSupport"; } + @Override public void onEnable(AbstractMenusApi api) {} + } + + private static final class StubApi implements AbstractMenusApi { + private final NodeSerializers serializers = NodeSerializers.defaults(); + private final TypeRegistry actions = new TypeRegistryImpl<>(serializers); + private final TypeRegistry rules = new TypeRegistryImpl<>(serializers); + private final TypeRegistry activators = new TypeRegistryImpl<>(serializers); + private final TypeRegistry itemProperties = new TypeRegistryImpl<>(serializers); + private final TypeRegistry> catalogs = new TypeRegistryImpl<>(serializers); + private final ProviderRegistry providers = new ProviderRegistryImpl(); + + @Override public TypeRegistry actions() { return actions; } + @Override public TypeRegistry rules() { return rules; } + @Override public TypeRegistry activators() { return activators; } + @Override public TypeRegistry itemProperties() { return itemProperties; } + @Override public TypeRegistry> catalogs() { return catalogs; } + @Override public ProviderRegistry providers() { return providers; } + @Override public NodeSerializers serializers() { return serializers; } + + @Override public VariableManager variables() { throw new UnsupportedOperationException(); } + @Override public org.bukkit.plugin.Plugin getPlugin() { throw new UnsupportedOperationException(); } + @Override public void loadMenus() { throw new UnsupportedOperationException(); } + @Override public void openMenu(Activator a, Object ctx, org.bukkit.entity.Player p, Menu m) { throw new UnsupportedOperationException(); } + @Override public void openMenu(org.bukkit.entity.Player p, Menu m) { throw new UnsupportedOperationException(); } + @Override public Optional getOpenedMenu(org.bukkit.entity.Player p) { throw new UnsupportedOperationException(); } + @Override public String apiVersion() { return "test"; } + } +} From 02688b1e45ccb7630494d261f58af4c337754d0c Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Thu, 23 Apr 2026 10:44:36 +0500 Subject: [PATCH 52/71] refactor(api)!: remove Handlers facade + AbstractMenus.registerProviders MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All provider registration now flows through ProviderRegistry — core providers via CoreProvidersBundle, external via addon MenuExtension.onEnable. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../java/ru/abstractmenus/api/Handlers.java | 97 ------------------- .../api/handler/EconomyHandler.java | 16 +-- .../java/ru/abstractmenus/AbstractMenus.java | 53 ---------- 3 files changed, 4 insertions(+), 162 deletions(-) delete mode 100644 api/src/main/java/ru/abstractmenus/api/Handlers.java diff --git a/api/src/main/java/ru/abstractmenus/api/Handlers.java b/api/src/main/java/ru/abstractmenus/api/Handlers.java deleted file mode 100644 index 1edcf7a..0000000 --- a/api/src/main/java/ru/abstractmenus/api/Handlers.java +++ /dev/null @@ -1,97 +0,0 @@ -package ru.abstractmenus.api; - -import ru.abstractmenus.api.handler.*; - -/** - * Data handlers provider - */ -public final class Handlers { - - private static EconomyHandler economyHandler; - private static PermissionsHandler permissionsHandler; - private static LevelHandler levelHandler; - private static PlaceholderHandler placeholderHandler; - private static SkinHandler skinHandler; - - private Handlers(){} - - /** - * Get registered economy handler - * @return Registered economy handler - */ - public static EconomyHandler getEconomyHandler() { - return economyHandler; - } - - /** - * Register own economy handler - * @param handler Economy handler implementation - */ - public static void setEconomyHandler(EconomyHandler handler) { - economyHandler = handler; - } - - /** - * Get registered permissions handler - * @return Registered permissions handler - */ - public static PermissionsHandler getPermissionsHandler() { - return permissionsHandler; - } - - /** - * Register own permissions handler - * @param handler Permissions handler implementation - */ - public static void setPermissionsHandler(PermissionsHandler handler) { - permissionsHandler = handler; - } - - /** - * Get registered level handler - * @return Registered level handler - */ - public static LevelHandler getLevelHandler() { - return levelHandler; - } - - /** - * Register own level handler - * @param handler Level handler implementation - */ - public static void setLevelHandler(LevelHandler handler) { - levelHandler = handler; - } - - /** - * Get registered placeholder handler - * @return Registered placeholder handler - */ - public static PlaceholderHandler getPlaceholderHandler() { - return placeholderHandler; - } - - /** - * Register own placeholder handler - * @param handler Placeholder handler implementation - */ - public static void setPlaceholderHandler(PlaceholderHandler handler) { - placeholderHandler = handler; - } - - /** - * Get registered skin handler - * @return Registered skin handler - */ - public static SkinHandler getSkinHandler() { - return skinHandler; - } - - /** - * Register own skin handler - * @param handler Skin handler implementation - */ - public static void setSkinHandler(SkinHandler handler) { - skinHandler = handler; - } -} diff --git a/api/src/main/java/ru/abstractmenus/api/handler/EconomyHandler.java b/api/src/main/java/ru/abstractmenus/api/handler/EconomyHandler.java index 7a7d426..382dec9 100644 --- a/api/src/main/java/ru/abstractmenus/api/handler/EconomyHandler.java +++ b/api/src/main/java/ru/abstractmenus/api/handler/EconomyHandler.java @@ -1,7 +1,6 @@ package ru.abstractmenus.api.handler; import org.bukkit.entity.Player; -import ru.abstractmenus.api.Handlers; /** * Handler for economy operations invoked by money-related menu actions and rules. @@ -20,15 +19,9 @@ * *

Registration

* - * A single handler is active at a time. Install yours in your plugin's - * {@code onEnable()}: - * - *
{@code
- * @Override
- * public void onEnable() {
- *     Handlers.setEconomyHandler(new PlayerPointsEconomy(playerPointsApi));
- * }
- * }
+ * A single handler is active at a time. Register yours via + * {@link ru.abstractmenus.api.ProviderRegistry#registerEconomy} inside your + * addon's {@link ru.abstractmenus.api.MenuExtension#onEnable}: * *

Example — bridging PlayerPoints

* @@ -83,8 +76,7 @@ * blocking IO; if the underlying economy plugin hits a database, cache the * last known balance and refresh asynchronously. * - * @see Handlers#setEconomyHandler(EconomyHandler) - * @see Handlers#getEconomyHandler() + * @see ru.abstractmenus.api.ProviderRegistry#registerEconomy * @see PermissionsHandler * @see LevelHandler */ diff --git a/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java b/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java index db4d3e0..ba39987 100644 --- a/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java +++ b/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java @@ -3,13 +3,10 @@ import com.tcoded.folialib.FoliaLib; import lombok.Getter; import lombok.Setter; -import net.luckperms.api.LuckPerms; -import net.milkbowl.vault.economy.Economy; import org.bukkit.Bukkit; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.RegisteredServiceProvider; import org.bukkit.plugin.ServicePriority; import org.bukkit.plugin.java.JavaPlugin; import ru.abstractmenus.api.*; @@ -33,9 +30,6 @@ import ru.abstractmenus.addon.AddonManager; import ru.abstractmenus.api.MenuExtension; import ru.abstractmenus.core.CoreExtension; -import ru.abstractmenus.handlers.*; -import ru.abstractmenus.handlers.placeholder.PlaceholderCustomHandler; -import ru.abstractmenus.handlers.placeholder.PlaceholderDefaultHandler; import ru.abstractmenus.hocon.api.ConfigurationLoader; import ru.abstractmenus.hocon.api.source.ConfigSources; import ru.abstractmenus.listeners.ChatListener; @@ -143,7 +137,6 @@ public void onEnable() { getServer().getServicesManager().register( AbstractMenusApi.class, api, this, ServicePriority.Normal); - registerProviders(); registerCommands(config); Serializers.init(this); @@ -248,52 +241,6 @@ private void disablePlugin() { getServer().getPluginManager().disablePlugin(this); } - private void registerProviders() { - if (checkDependency("Vault")) { - RegisteredServiceProvider economyProvider = getServer().getServicesManager().getRegistration(Economy.class); - - if (economyProvider != null) { - Handlers.setEconomyHandler(new EconomyVaultHandler(economyProvider.getProvider())); - } else { - Logger.warning("Economy plugin doesn't installed"); - } - } else { - Logger.warning("Vault doesn't installed. Economy actions and rules won't work"); - } - - if (checkDependency("LuckPerms")) { - RegisteredServiceProvider provider = Bukkit.getServicesManager().getRegistration(LuckPerms.class); - - if (provider != null) { - Handlers.setPermissionsHandler(new LuckPermsHandler(provider.getProvider())); - Logger.info("Using LuckPerms"); - } else { - Logger.severe("Cannot find registered LuckPerms service"); - } - } else { - Handlers.setPermissionsHandler(new PermissionDefaultHandler(this)); - Logger.info("Using bundled temporary permissions manager"); - Logger.warning("LuckPerms doesn't installed. After reload all assigned permissions will be removed"); - } - - Handlers.setLevelHandler(new LevelDefaultHandler()); - - if (checkDependency("PlaceholderAPI")) { - Handlers.setPlaceholderHandler(new PlaceholderCustomHandler()); - Logger.info("Using PlaceholderAPI"); - } else { - Handlers.setPlaceholderHandler(new PlaceholderDefaultHandler()); - Logger.info("Using bundled placeholders"); - } - - AbstractMenusApi.get().providers().placeholders().registerAll(); - - if (checkDependency("SkinsRestorer")) { - Handlers.setSkinHandler(new SkinsRestorerHandler(isProxyMode, this)); - Logger.info("Using SkinsRestorer as skins provider"); - } - } - public static boolean checkDependency(String plugin) { return Bukkit.getServer().getPluginManager().isPluginEnabled(plugin); } From 5c92ab5985410aaba2eb5d74e453ec20977836bf Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Thu, 23 Apr 2026 10:55:03 +0500 Subject: [PATCH 53/71] feat(plugin): ProviderRegistryImpl consults config defaults before auto-resolve New setConfigDefaults(Function) hook lets the impl prefer a server-configured provider id per section. "auto" or null falls through to priority-based auto-resolution. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../api/ProviderRegistryImpl.java | 31 ++++++++++++++--- .../api/ProviderRegistryImplTest.java | 33 +++++++++++++++++++ 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/plugin/src/main/java/ru/abstractmenus/api/ProviderRegistryImpl.java b/plugin/src/main/java/ru/abstractmenus/api/ProviderRegistryImpl.java index 77a4ad8..50ae7b4 100644 --- a/plugin/src/main/java/ru/abstractmenus/api/ProviderRegistryImpl.java +++ b/plugin/src/main/java/ru/abstractmenus/api/ProviderRegistryImpl.java @@ -15,6 +15,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Function; /** * Default {@link ProviderRegistry} implementation. Five sections sharing an @@ -33,10 +34,30 @@ public final class ProviderRegistryImpl implements ProviderRegistry { private final Section placeholders = new Section<>(); private final Section skins = new Section<>(); + /** section kind → configured-default id (e.g. "economy" → "playerpoints"). */ + private Function configDefaults = kind -> null; // no-op by default + + /** Wire up the config-backed default source. Called once from AbstractMenusApiImpl. */ + public void setConfigDefaults(Function lookup) { + this.configDefaults = lookup; + } + + // ---- Config-default resolution helper -------------------------------- + + private T resolveWithConfig(String kind, Section section) { + String configured = configDefaults.apply(kind); + if (configured != null && !configured.equalsIgnoreCase("auto")) { + T h = section.byId(configured); + if (h != null) return h; + // Configured id not found — fall back to auto. + } + return section.auto(); + } + // ---- Economy --------------------------------------------------------- @Override public void registerEconomy(String id, EconomyHandler h, int pr, MenuExtension o) { economy.put(id, h, pr, o); } - @Override public EconomyHandler economy() { return economy.auto(); } + @Override public EconomyHandler economy() { return resolveWithConfig("economy", economy); } @Override public EconomyHandler economy(String id) { return economy.byId(id); } @Override public Collection allEconomy() { return economy.all(); } @Override public boolean hasEconomy(String id) { return economy.has(id); } @@ -44,7 +65,7 @@ public final class ProviderRegistryImpl implements ProviderRegistry { // ---- Permissions ----------------------------------------------------- @Override public void registerPermissions(String id, PermissionsHandler h, int pr, MenuExtension o) { permissions.put(id, h, pr, o); } - @Override public PermissionsHandler permissions() { return permissions.auto(); } + @Override public PermissionsHandler permissions() { return resolveWithConfig("permissions", permissions); } @Override public PermissionsHandler permissions(String id) { return permissions.byId(id); } @Override public Collection allPermissions() { return permissions.all(); } @Override public boolean hasPermissions(String id) { return permissions.has(id); } @@ -52,7 +73,7 @@ public final class ProviderRegistryImpl implements ProviderRegistry { // ---- Levels ---------------------------------------------------------- @Override public void registerLevels(String id, LevelHandler h, int pr, MenuExtension o) { levels.put(id, h, pr, o); } - @Override public LevelHandler levels() { return levels.auto(); } + @Override public LevelHandler levels() { return resolveWithConfig("levels", levels); } @Override public LevelHandler levels(String id) { return levels.byId(id); } @Override public Collection allLevels() { return levels.all(); } @Override public boolean hasLevels(String id) { return levels.has(id); } @@ -60,7 +81,7 @@ public final class ProviderRegistryImpl implements ProviderRegistry { // ---- Placeholders ---------------------------------------------------- @Override public void registerPlaceholders(String id, PlaceholderHandler h, int pr, MenuExtension o) { placeholders.put(id, h, pr, o); } - @Override public PlaceholderHandler placeholders() { return placeholders.auto(); } + @Override public PlaceholderHandler placeholders() { return resolveWithConfig("placeholders", placeholders); } @Override public PlaceholderHandler placeholders(String id) { return placeholders.byId(id); } @Override public Collection allPlaceholders() { return placeholders.all(); } @Override public boolean hasPlaceholders(String id) { return placeholders.has(id); } @@ -68,7 +89,7 @@ public final class ProviderRegistryImpl implements ProviderRegistry { // ---- Skins ----------------------------------------------------------- @Override public void registerSkins(String id, SkinHandler h, int pr, MenuExtension o) { skins.put(id, h, pr, o); } - @Override public SkinHandler skins() { return skins.auto(); } + @Override public SkinHandler skins() { return resolveWithConfig("skins", skins); } @Override public SkinHandler skins(String id) { return skins.byId(id); } @Override public Collection allSkins() { return skins.all(); } @Override public boolean hasSkins(String id) { return skins.has(id); } diff --git a/plugin/src/test/java/ru/abstractmenus/api/ProviderRegistryImplTest.java b/plugin/src/test/java/ru/abstractmenus/api/ProviderRegistryImplTest.java index 37c10f0..a113940 100644 --- a/plugin/src/test/java/ru/abstractmenus/api/ProviderRegistryImplTest.java +++ b/plugin/src/test/java/ru/abstractmenus/api/ProviderRegistryImplTest.java @@ -102,6 +102,39 @@ void sectionsAreIndependent() { assertNull(registry.skins()); } + @Test + void configDefault_prefersConfiguredId() { + EconomyHandler vault = mock(EconomyHandler.class); + EconomyHandler pp = mock(EconomyHandler.class); + registry.registerEconomy("vault", vault, 50, ownerA); + registry.registerEconomy("playerpoints", pp, 100, ownerA); + + // Without config, auto prefers playerpoints (priority 100). + assertSame(pp, registry.economy()); + + // With config override to vault, vault wins despite lower priority. + registry.setConfigDefaults(kind -> "economy".equals(kind) ? "vault" : null); + assertSame(vault, registry.economy()); + } + + @Test + void configDefault_autoKeyword_fallsBackToAuto() { + EconomyHandler vault = mock(EconomyHandler.class); + registry.registerEconomy("vault", vault, 50, ownerA); + + registry.setConfigDefaults(kind -> "auto"); + assertSame(vault, registry.economy()); + } + + @Test + void configDefault_unknownId_fallsBackToAuto() { + EconomyHandler vault = mock(EconomyHandler.class); + registry.registerEconomy("vault", vault, 50, ownerA); + + registry.setConfigDefaults(kind -> "ghost"); // not registered + assertSame(vault, registry.economy()); // auto fallback + } + // --- helper --- static class DummyExtension implements MenuExtension { From 94ebd3ec33d05aba21a6e7a929a9d1fdd1d631e6 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Thu, 23 Apr 2026 10:56:49 +0500 Subject: [PATCH 54/71] feat(plugin): config.conf providers{} block + MainConfig.providerDefault Defaults to "auto" for every section when absent from user config. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/main/java/ru/abstractmenus/MainConfig.java | 14 ++++++++++++++ plugin/src/main/resources/config.conf | 12 ++++++++++++ 2 files changed, 26 insertions(+) diff --git a/plugin/src/main/java/ru/abstractmenus/MainConfig.java b/plugin/src/main/java/ru/abstractmenus/MainConfig.java index 6c304d2..d4cd3f9 100644 --- a/plugin/src/main/java/ru/abstractmenus/MainConfig.java +++ b/plugin/src/main/java/ru/abstractmenus/MainConfig.java @@ -6,6 +6,9 @@ import java.nio.file.Path; import java.nio.file.Paths; +import java.util.HashMap; +import java.util.List; +import java.util.Map; @Getter public final class MainConfig { @@ -26,6 +29,8 @@ public final class MainConfig { private Path menusFolder; private Path dbFolder; + private final Map providerDefaults = new HashMap<>(); + public void load(Plugin plugin, ConfigNode node) { useVariables = node.node("variables").getBoolean(true); syncVariables = node.node("syncVariables").getBoolean(false); @@ -55,5 +60,14 @@ public void load(Plugin plugin, ConfigNode node) { } else { dbFolder = plugin.getDataFolder().toPath(); } + + for (String kind : List.of("economy", "permissions", "levels", "placeholders", "skins")) { + String val = node.node("providers").node(kind).getString("auto"); + providerDefaults.put(kind, val); + } + } + + public String providerDefault(String kind) { + return providerDefaults.getOrDefault(kind, "auto"); } } diff --git a/plugin/src/main/resources/config.conf b/plugin/src/main/resources/config.conf index 1775716..db9dff4 100644 --- a/plugin/src/main/resources/config.conf +++ b/plugin/src/main/resources/config.conf @@ -37,4 +37,16 @@ time { hour: "h" minute: "min" second: "sec" +} + +# Default provider selection. Values: an explicit provider id (e.g. +# "vault", "playerpoints") or "auto" to use priority-based resolution +# (highest-priority registered provider wins). Per-action overrides in +# menus (via `provider: "..."`) always take precedence over this default. +providers { + economy = "auto" + permissions = "auto" + levels = "auto" + placeholders = "auto" + skins = "auto" } \ No newline at end of file From 85cb6225e7c9a466cc1200ca37fe924f650fe3fb Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Thu, 23 Apr 2026 10:58:52 +0500 Subject: [PATCH 55/71] feat(plugin): wire MainConfig.providerDefault into ProviderRegistryImpl Server-configured provider defaults now take effect at runtime: config.conf `providers { economy = "playerpoints" }` makes .economy() prefer PP even when another provider has higher priority. Co-Authored-By: Claude Opus 4.7 (1M context) --- plugin/src/main/java/ru/abstractmenus/AbstractMenus.java | 4 +++- .../main/java/ru/abstractmenus/api/AbstractMenusApiImpl.java | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java b/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java index ba39987..cc34cd0 100644 --- a/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java +++ b/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java @@ -66,6 +66,7 @@ public final class AbstractMenus extends JavaPlugin { private FoliaLib foliaLib; private AbstractMenusApi api; private AddonManager addonManager; + private MainConfig mainConfig; @Getter @Setter @@ -100,7 +101,8 @@ public void onEnable() { metrics = new Metrics(this); foliaLib = new FoliaLib(this); - MainConfig config = new MainConfig(); + mainConfig = new MainConfig(); + MainConfig config = mainConfig; config.load(this, ConfigurationLoader.builder() .source(ConfigSources.resource("/config.conf", this) diff --git a/plugin/src/main/java/ru/abstractmenus/api/AbstractMenusApiImpl.java b/plugin/src/main/java/ru/abstractmenus/api/AbstractMenusApiImpl.java index 5b9b755..4fdcbda 100644 --- a/plugin/src/main/java/ru/abstractmenus/api/AbstractMenusApiImpl.java +++ b/plugin/src/main/java/ru/abstractmenus/api/AbstractMenusApiImpl.java @@ -34,7 +34,9 @@ public AbstractMenusApiImpl(AbstractMenus plugin) { this.activators = new TypeRegistryImpl<>(serializers); this.itemProperties = new TypeRegistryImpl<>(serializers); this.catalogs = new TypeRegistryImpl<>(serializers); - this.providers = new ProviderRegistryImpl(); + ProviderRegistryImpl providerImpl = new ProviderRegistryImpl(); + providerImpl.setConfigDefaults(kind -> plugin.getMainConfig().providerDefault(kind)); + this.providers = providerImpl; } @Override public TypeRegistry actions() { return actions; } From 65a8e1bf922f5423e269de47e363b98861f944e0 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Thu, 23 Apr 2026 11:15:01 +0500 Subject: [PATCH 56/71] feat(data): economy actions/rule accept optional provider: HOCON field * takeMoney / giveMoney / hasMoney read optional `provider` string * Serializer fails-at-load if provider id is not registered * Runtime resolution: explicit provider via providers().economy(id), else default via providers().economy() (respects config providers{}) Co-Authored-By: Claude Opus 4.7 (1M context) --- .../data/actions/ActionMoneyGive.java | 38 ++++++++++++++++-- .../data/actions/ActionMoneyTake.java | 38 ++++++++++++++++-- .../abstractmenus/data/rules/RuleMoney.java | 39 ++++++++++++++++--- 3 files changed, 102 insertions(+), 13 deletions(-) diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMoneyGive.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMoneyGive.java index 5710837..6657241 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMoneyGive.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMoneyGive.java @@ -7,6 +7,7 @@ import ru.abstractmenus.hocon.api.serialize.NodeSerializer; import org.bukkit.entity.Player; import ru.abstractmenus.api.Action; +import ru.abstractmenus.api.handler.EconomyHandler; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.AbstractMenusApi; @@ -14,23 +15,52 @@ public class ActionMoneyGive implements Action { private final TypeDouble money; + private final String provider; - private ActionMoneyGive(TypeDouble money) { + private ActionMoneyGive(TypeDouble money, String provider) { this.money = money; + this.provider = provider; } @Override public void activate(Player player, Menu menu, Item clickedItem) { - if (AbstractMenusApi.get().providers().economy() != null) { - AbstractMenusApi.get().providers().economy().giveBalance(player, money.getDouble(player, menu)); + EconomyHandler eco = provider != null + ? AbstractMenusApi.get().providers().economy(provider) + : AbstractMenusApi.get().providers().economy(); + if (eco == null) { + return; } + eco.giveBalance(player, money.getDouble(player, menu)); } public static class Serializer implements NodeSerializer { @Override public ActionMoneyGive deserialize(Class type, ConfigNode node) throws NodeSerializeException { - return new ActionMoneyGive(node.getValue(TypeDouble.class)); + TypeDouble money; + String provider = null; + + if (node.isMap()) { + money = node.node("amount").getValue(TypeDouble.class); + provider = node.node("provider").getString(null); + } else { + money = node.getValue(TypeDouble.class); + } + + if (provider != null) { + if (!AbstractMenusApi.get().providers().hasEconomy(provider)) { + StringBuilder known = new StringBuilder(); + for (EconomyHandler h : AbstractMenusApi.get().providers().allEconomy()) { + if (known.length() > 0) known.append(", "); + known.append(h.getClass().getSimpleName()); + } + throw new NodeSerializeException(node, + "Unknown economy provider '" + provider + "'. Registered: [" + + known + "]. Omit the 'provider' field for default selection."); + } + } + + return new ActionMoneyGive(money, provider); } } diff --git a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMoneyTake.java b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMoneyTake.java index c9b543f..757c2fe 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMoneyTake.java +++ b/plugin/src/main/java/ru/abstractmenus/data/actions/ActionMoneyTake.java @@ -7,6 +7,7 @@ import ru.abstractmenus.hocon.api.serialize.NodeSerializer; import org.bukkit.entity.Player; import ru.abstractmenus.api.Action; +import ru.abstractmenus.api.handler.EconomyHandler; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.AbstractMenusApi; @@ -14,23 +15,52 @@ public class ActionMoneyTake implements Action { private final TypeDouble money; + private final String provider; - private ActionMoneyTake(TypeDouble money) { + private ActionMoneyTake(TypeDouble money, String provider) { this.money = money; + this.provider = provider; } @Override public void activate(Player player, Menu menu, Item clickedItem) { - if (AbstractMenusApi.get().providers().economy() != null) { - AbstractMenusApi.get().providers().economy().takeBalance(player, money.getDouble(player, menu)); + EconomyHandler eco = provider != null + ? AbstractMenusApi.get().providers().economy(provider) + : AbstractMenusApi.get().providers().economy(); + if (eco == null) { + return; } + eco.takeBalance(player, money.getDouble(player, menu)); } public static class Serializer implements NodeSerializer { @Override public ActionMoneyTake deserialize(Class type, ConfigNode node) throws NodeSerializeException { - return new ActionMoneyTake(node.getValue(TypeDouble.class)); + TypeDouble money; + String provider = null; + + if (node.isMap()) { + money = node.node("amount").getValue(TypeDouble.class); + provider = node.node("provider").getString(null); + } else { + money = node.getValue(TypeDouble.class); + } + + if (provider != null) { + if (!AbstractMenusApi.get().providers().hasEconomy(provider)) { + StringBuilder known = new StringBuilder(); + for (EconomyHandler h : AbstractMenusApi.get().providers().allEconomy()) { + if (known.length() > 0) known.append(", "); + known.append(h.getClass().getSimpleName()); + } + throw new NodeSerializeException(node, + "Unknown economy provider '" + provider + "'. Registered: [" + + known + "]. Omit the 'provider' field for default selection."); + } + } + + return new ActionMoneyTake(money, provider); } } diff --git a/plugin/src/main/java/ru/abstractmenus/data/rules/RuleMoney.java b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleMoney.java index e9be945..01ec2b5 100644 --- a/plugin/src/main/java/ru/abstractmenus/data/rules/RuleMoney.java +++ b/plugin/src/main/java/ru/abstractmenus/data/rules/RuleMoney.java @@ -5,6 +5,7 @@ import ru.abstractmenus.hocon.api.serialize.NodeSerializer; import org.bukkit.entity.Player; import ru.abstractmenus.api.Rule; +import ru.abstractmenus.api.handler.EconomyHandler; import ru.abstractmenus.api.inventory.Menu; import ru.abstractmenus.api.inventory.Item; import ru.abstractmenus.api.AbstractMenusApi; @@ -13,24 +14,52 @@ public class RuleMoney implements Rule { private final TypeDouble money; + private final String provider; - private RuleMoney(TypeDouble money){ + private RuleMoney(TypeDouble money, String provider){ this.money = money; + this.provider = provider; } @Override public boolean check(Player player, Menu menu, Item clickedItem) { - if(AbstractMenusApi.get().providers().economy() != null){ - return AbstractMenusApi.get().providers().economy().hasBalance(player, money.getDouble(player, menu)); + EconomyHandler eco = provider != null + ? AbstractMenusApi.get().providers().economy(provider) + : AbstractMenusApi.get().providers().economy(); + if (eco == null) { + return false; } - return false; + return eco.hasBalance(player, money.getDouble(player, menu)); } public static class Serializer implements NodeSerializer { @Override public RuleMoney deserialize(Class type, ConfigNode node) throws NodeSerializeException { - return new RuleMoney(node.getValue(TypeDouble.class)); + TypeDouble money; + String provider = null; + + if (node.isMap()) { + money = node.node("amount").getValue(TypeDouble.class); + provider = node.node("provider").getString(null); + } else { + money = node.getValue(TypeDouble.class); + } + + if (provider != null) { + if (!AbstractMenusApi.get().providers().hasEconomy(provider)) { + StringBuilder known = new StringBuilder(); + for (EconomyHandler h : AbstractMenusApi.get().providers().allEconomy()) { + if (known.length() > 0) known.append(", "); + known.append(h.getClass().getSimpleName()); + } + throw new NodeSerializeException(node, + "Unknown economy provider '" + provider + "'. Registered: [" + + known + "]. Omit the 'provider' field for default selection."); + } + } + + return new RuleMoney(money, provider); } } From 263763b3a06e79316e6f61f83aaddac6d960ed3e Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Thu, 23 Apr 2026 11:23:09 +0500 Subject: [PATCH 57/71] =?UTF-8?q?test(data):=20MoneyProviderSelectionTest?= =?UTF-8?q?=20=E2=80=94=20provider:=20selection=20+=20fail-at-load?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Covers explicit provider selection, auto-fallback on omission, scalar-form backward compat, and fail-at-load on unknown provider id. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../data/MoneyProviderSelectionTest.java | 216 ++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 plugin/src/test/java/ru/abstractmenus/data/MoneyProviderSelectionTest.java diff --git a/plugin/src/test/java/ru/abstractmenus/data/MoneyProviderSelectionTest.java b/plugin/src/test/java/ru/abstractmenus/data/MoneyProviderSelectionTest.java new file mode 100644 index 0000000..4339271 --- /dev/null +++ b/plugin/src/test/java/ru/abstractmenus/data/MoneyProviderSelectionTest.java @@ -0,0 +1,216 @@ +package ru.abstractmenus.data; + +import org.bukkit.entity.Player; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import ru.abstractmenus.api.handler.EconomyHandler; +import ru.abstractmenus.data.actions.ActionMoneyGive; +import ru.abstractmenus.data.actions.ActionMoneyTake; +import ru.abstractmenus.data.rules.RuleMoney; +import ru.abstractmenus.datatype.TypeDouble; +import ru.abstractmenus.hocon.api.ConfigNode; +import ru.abstractmenus.hocon.api.ConfigurationLoader; +import ru.abstractmenus.hocon.api.serialize.NodeSerializeException; +import ru.abstractmenus.hocon.api.source.ConfigSources; +import ru.abstractmenus.testsupport.ApiTestSupport; + +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +/** + * End-to-end tests for provider: selection in ActionMoneyTake / ActionMoneyGive / RuleMoney. + * + *

Covers: + *

    + *
  1. Explicit {@code provider: "vault"} routes to the named handler.
  2. + *
  3. Omitted provider auto-resolves to the highest-priority handler.
  4. + *
  5. Scalar (legacy) form {@code takeMoney: 100.0} still deserializes.
  6. + *
  7. Unknown provider id throws {@link NodeSerializeException} at deserialize time.
  8. + *
+ */ +class MoneyProviderSelectionTest { + + /** vault: priority 50, pp: priority 100 — pp wins auto-resolve. */ + private static final int VAULT_PRIORITY = 50; + private static final int PP_PRIORITY = 100; + + private ApiTestSupport support; + private EconomyHandler vault; + private EconomyHandler pp; + + @BeforeEach + void setUp() { + support = ApiTestSupport.install(); + + vault = mock(EconomyHandler.class); + pp = mock(EconomyHandler.class); + + // Register TypeDouble.Serializer so the HOCON deserialization pipeline + // can resolve 'amount' nodes. In production this is done in Serializers.init(). + support.api().serializers().register(TypeDouble.class, new TypeDouble.Serializer()); + + support.providers().registerEconomy("vault", vault, VAULT_PRIORITY, support.owner()); + support.providers().registerEconomy("playerpoints", pp, PP_PRIORITY, support.owner()); + } + + @AfterEach + void tearDown() { + support.close(); + } + + // ------------------------------------------------------------------------- + // ActionMoneyTake + // ------------------------------------------------------------------------- + + @Test + void take_explicitProvider_usesNamedHandler() throws Exception { + String hocon = "amount = 100\nprovider = \"vault\""; + ActionMoneyTake action = new ActionMoneyTake.Serializer() + .deserialize(ActionMoneyTake.class, parseMap(hocon)); + + Player player = mock(Player.class); + action.activate(player, null, null); + + verify(vault).takeBalance(eq(player), eq(100.0)); + verify(pp, never()).takeBalance(any(), anyDouble()); + } + + @Test + void take_omittedProvider_usesHighestPriority() throws Exception { + // map form without provider field → auto-resolve → pp (priority 100 > 50) + String hocon = "amount = 50"; + ActionMoneyTake action = new ActionMoneyTake.Serializer() + .deserialize(ActionMoneyTake.class, parseMap(hocon)); + + Player player = mock(Player.class); + action.activate(player, null, null); + + verify(pp).takeBalance(eq(player), eq(50.0)); + verify(vault, never()).takeBalance(any(), anyDouble()); + } + + @Test + void take_scalarForm_backwardCompat() throws Exception { + // Legacy scalar: takeMoney: 100.0 — the node is a bare number, not a map. + ActionMoneyTake action = new ActionMoneyTake.Serializer() + .deserialize(ActionMoneyTake.class, parseScalar("100.0")); + + Player player = mock(Player.class); + action.activate(player, null, null); + + // Scalar form → no explicit provider → auto-resolve → pp + verify(pp).takeBalance(eq(player), eq(100.0)); + } + + @Test + void take_unknownProvider_throwsAtDeserialize() { + String hocon = "amount = 100\nprovider = \"ghost\""; + var serializer = new ActionMoneyTake.Serializer(); + + NodeSerializeException ex = assertThrows(NodeSerializeException.class, + () -> serializer.deserialize(ActionMoneyTake.class, parseMap(hocon))); + assertTrue(ex.getMessage().toLowerCase().contains("ghost"), + "error message should mention the unknown provider id; got: " + ex.getMessage()); + } + + // ------------------------------------------------------------------------- + // ActionMoneyGive + // ------------------------------------------------------------------------- + + @Test + void give_explicitProvider_usesNamedHandler() throws Exception { + String hocon = "amount = 200\nprovider = \"playerpoints\""; + ActionMoneyGive action = new ActionMoneyGive.Serializer() + .deserialize(ActionMoneyGive.class, parseMap(hocon)); + + Player player = mock(Player.class); + action.activate(player, null, null); + + verify(pp).giveBalance(eq(player), eq(200.0)); + verify(vault, never()).giveBalance(any(), anyDouble()); + } + + @Test + void give_unknownProvider_throwsAtDeserialize() { + String hocon = "amount = 200\nprovider = \"nowhere\""; + var serializer = new ActionMoneyGive.Serializer(); + + NodeSerializeException ex = assertThrows(NodeSerializeException.class, + () -> serializer.deserialize(ActionMoneyGive.class, parseMap(hocon))); + assertTrue(ex.getMessage().toLowerCase().contains("nowhere"), + "error message should mention the unknown provider id; got: " + ex.getMessage()); + } + + // ------------------------------------------------------------------------- + // RuleMoney + // ------------------------------------------------------------------------- + + @Test + void rule_explicitProvider_checksNamedHandler() throws Exception { + when(vault.hasBalance(any(), anyDouble())).thenReturn(true); + when(pp.hasBalance(any(), anyDouble())).thenReturn(false); + + String hocon = "amount = 500\nprovider = \"vault\""; + RuleMoney rule = new RuleMoney.Serializer() + .deserialize(RuleMoney.class, parseMap(hocon)); + + Player player = mock(Player.class); + boolean result = rule.check(player, null, null); + + assertTrue(result, "vault says true — rule should pass"); + verify(vault).hasBalance(eq(player), eq(500.0)); + verify(pp, never()).hasBalance(any(), anyDouble()); + } + + @Test + void rule_unknownProvider_throwsAtDeserialize() { + String hocon = "amount = 100\nprovider = \"missing\""; + var serializer = new RuleMoney.Serializer(); + + NodeSerializeException ex = assertThrows(NodeSerializeException.class, + () -> serializer.deserialize(RuleMoney.class, parseMap(hocon))); + assertTrue(ex.getMessage().toLowerCase().contains("missing"), + "error message should mention the unknown provider id; got: " + ex.getMessage()); + } + + // ------------------------------------------------------------------------- + // Parse helpers — use the test-support serializers so TypeDouble is known. + // ------------------------------------------------------------------------- + + /** + * Parse a HOCON snippet that represents a map (key = value pairs) into a + * ConfigNode using the same {@link ru.abstractmenus.hocon.api.serialize.NodeSerializers} + * instance that has {@code TypeDouble} registered. + */ + private ConfigNode parseMap(String hocon) throws Exception { + String wrapped = "val {\n" + hocon + "\n}"; + byte[] bytes = wrapped.getBytes(StandardCharsets.UTF_8); + ConfigNode root = ConfigurationLoader.builder() + .source(ConfigSources.inputStream("test", new ByteArrayInputStream(bytes))) + .serializers(support.api().serializers()) + .build() + .load(); + return root.node("val"); + } + + /** + * Parse a bare scalar value (e.g. {@code "100.0"}) into a ConfigNode that + * represents that scalar — mirrors how the legacy takeMoney: 100 form + * arrives at the Serializer (the node IS the value, not a map). + */ + private ConfigNode parseScalar(String scalar) throws Exception { + String wrapped = "val = " + scalar; + byte[] bytes = wrapped.getBytes(StandardCharsets.UTF_8); + ConfigNode root = ConfigurationLoader.builder() + .source(ConfigSources.inputStream("test", new ByteArrayInputStream(bytes))) + .serializers(support.api().serializers()) + .build() + .load(); + return root.node("val"); + } +} From daa2c285818489d8eb3d23fc01528f0683caa4ed Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Thu, 23 Apr 2026 11:35:35 +0500 Subject: [PATCH 58/71] docs(api): rich javadoc on Level/Permissions/Placeholder/Skin handlers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirrors the style established by EconomyHandler — class-level overview, registration example, impl bridge example, menu usage, threading notes, per-method @param/@return/@implNote. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../api/handler/LevelHandler.java | 170 +++++++++++++-- .../api/handler/PermissionsHandler.java | 201 ++++++++++++++++-- .../api/handler/PlaceholderHandler.java | 170 +++++++++++++-- .../api/handler/SkinHandler.java | 134 +++++++++++- 4 files changed, 614 insertions(+), 61 deletions(-) diff --git a/api/src/main/java/ru/abstractmenus/api/handler/LevelHandler.java b/api/src/main/java/ru/abstractmenus/api/handler/LevelHandler.java index 38aae38..49f6c19 100644 --- a/api/src/main/java/ru/abstractmenus/api/handler/LevelHandler.java +++ b/api/src/main/java/ru/abstractmenus/api/handler/LevelHandler.java @@ -3,49 +3,181 @@ import org.bukkit.entity.Player; /** - * Level handler needs for level actions and rules + * Handler for experience and level operations invoked by XP/level-related menu + * actions and rules. + * + *

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

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

Registration

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

Example — bridging MMOCore

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

Menu usage

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

Threading

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

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

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

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

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

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

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

Registration

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

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

Example — bridging LuckPerms

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

Menu usage

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

Threading

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

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

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

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

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

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

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

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

Registration

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

Example — bridging PlaceholderAPI

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

Menu usage

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

Threading

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

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

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

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

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

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

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

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

Registration

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

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

Example — bridging SkinsRestorer

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

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

Menu usage

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

Threading

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

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

If the player never had a custom skin applied the call is a no-op. + * + * @param player the player whose skin is being reset; never {@code null}, + * always online + * + * @implNote Implementations that cache applied skins should clear the + * entry for this player so a subsequent {@code setSkin} call + * reflects the new value rather than a stale cache. */ void resetSkin(Player player); From d449d63b4d8e1b858e72431f3e1f74e6d655eb2c Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Thu, 23 Apr 2026 11:40:09 +0500 Subject: [PATCH 59/71] docs(api): rich javadoc on core root interfaces (Action/Rule/Activator/Catalog) + utilities MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Matches the EconomyHandler / TypeRegistry style — class-level overview, addon-impl example with inner Serializer, menu usage where applicable, per-method @param / @return / @implNote. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../java/ru/abstractmenus/api/Action.java | 83 ++++++++++++- .../java/ru/abstractmenus/api/Activator.java | 114 ++++++++++++++++-- .../java/ru/abstractmenus/api/Catalog.java | 106 ++++++++++++++-- .../java/ru/abstractmenus/api/Logger.java | 60 +++++++-- .../main/java/ru/abstractmenus/api/Rule.java | 89 +++++++++++++- .../ru/abstractmenus/api/ValueExtractor.java | 54 +++++++-- 6 files changed, 461 insertions(+), 45 deletions(-) diff --git a/api/src/main/java/ru/abstractmenus/api/Action.java b/api/src/main/java/ru/abstractmenus/api/Action.java index 36a52b6..c73b55d 100644 --- a/api/src/main/java/ru/abstractmenus/api/Action.java +++ b/api/src/main/java/ru/abstractmenus/api/Action.java @@ -5,16 +5,89 @@ import ru.abstractmenus.api.inventory.Item; /** - * Represents menu action. + * Executable menu action — the imperative "do something" block attached to + * a menu item, a menu itself, or an {@link Activator}. + * + *

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

Example — a webhook action

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

Menu usage

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

Threading

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

Called by AbstractMenus when the action's enclosing list fires — + * typically a click on a menu item, an activator trigger, or a menu open / + * close hook. + * + * @param player the player who triggered the action; never + * {@code null} and always online + * @param menu the menu in which the action fires; never {@code null} + * @param clickedItem the item that triggered the action, or {@code null} + * when the action was fired by something other than an + * item click (activator, menu-level hook) + * + * @implNote Runs on the main server thread. Do not block on IO — + * schedule long-running work asynchronously. */ void activate(Player player, Menu menu, Item clickedItem); diff --git a/api/src/main/java/ru/abstractmenus/api/Activator.java b/api/src/main/java/ru/abstractmenus/api/Activator.java index 9657857..5361991 100644 --- a/api/src/main/java/ru/abstractmenus/api/Activator.java +++ b/api/src/main/java/ru/abstractmenus/api/Activator.java @@ -5,36 +5,130 @@ import ru.abstractmenus.api.inventory.Menu; /** - * Menu activator. Activator will be registered as event listener. - * Do not register it manually, plugin does this automatically + * Event-driven trigger that opens a menu in response to a game event — + * a command, a chat line, an NPC click, a region boundary crossing, a sign + * interaction, and so on. + * + *

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

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

Example — open a menu on resource-pack load

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

Menu usage

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

Threading

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

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

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

Override to expose event-specific fields as menu placeholders (for + * instance, the clicked entity's UUID or a region name). + * + * @return the extractor, or {@code null} if this activator does not + * contribute any context placeholders (the default) */ public ValueExtractor getValueExtractor() { return null; diff --git a/api/src/main/java/ru/abstractmenus/api/Catalog.java b/api/src/main/java/ru/abstractmenus/api/Catalog.java index 9f7a962..2713681 100644 --- a/api/src/main/java/ru/abstractmenus/api/Catalog.java +++ b/api/src/main/java/ru/abstractmenus/api/Catalog.java @@ -6,22 +6,112 @@ import java.util.Collection; /** - * Catalog is an object collection provider - * Catalog uses for the auto generated menus + * Source of data collections rendered across inventory slots by + * {@code generatedMenu} menus. + * + *

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

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

Example — a clan roster catalog

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

Menu usage

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

Threading

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

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

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

Example

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

Threading

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

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

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

Use for failures that prevent a menu / action / rule from functioning + * — IO errors, schema violations, unhandled exceptions. + * + * @param message the message to log */ public static void severe(String message){ logger.severe(message); diff --git a/api/src/main/java/ru/abstractmenus/api/Rule.java b/api/src/main/java/ru/abstractmenus/api/Rule.java index 49ce929..5ccc1a2 100644 --- a/api/src/main/java/ru/abstractmenus/api/Rule.java +++ b/api/src/main/java/ru/abstractmenus/api/Rule.java @@ -5,17 +5,94 @@ import ru.abstractmenus.api.inventory.Item; /** - * Represents the rule that the player must meets in order to proceed. + * Boolean predicate attached to a menu, item, or action — decides whether + * the enclosing element is visible, clickable, or allowed to fire. + * + *

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

Example — a cooldown rule

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

Menu usage

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

Threading & purity

+ * + * {@link #check(Player, Menu, Item)} runs on the main server thread and must + * be a pure predicate — do not mutate state here. It is called + * frequently (on menu render, tick updates, every item click) so keep it + * cheap; cache anything that hits IO. + * + * @see Action + * @see Activator + * @see TypeRegistry + * @see AbstractMenusApi#rules() */ @FunctionalInterface public interface Rule { /** - * Check if the player meets the rule - * @param player Player to check - * @param menu Menu in which this rule works - * @param clickedItem An item which player might click and initiate this checking. Might be null - * @return true if player meets the rule or false of not + * Evaluate the predicate for a given player / menu / item context. + * + *

Called whenever AbstractMenus needs to decide whether the element + * wrapping this rule should show, fire, or be clickable. Multiple rules on + * the same element are AND-combined. + * + * @param player the player being evaluated; never {@code null} and + * always online + * @param menu the menu the rule is evaluated against; never + * {@code null} + * @param clickedItem the item involved when the rule is evaluated as part + * of a click, or {@code null} for menu-level / + * item-render evaluation + * @return {@code true} if the player satisfies the rule, {@code false} + * otherwise + * + * @implNote Must be side-effect free and cheap — runs on the main + * thread and is polled frequently. Do not block on IO. */ boolean check(Player player, Menu menu, Item clickedItem); diff --git a/api/src/main/java/ru/abstractmenus/api/ValueExtractor.java b/api/src/main/java/ru/abstractmenus/api/ValueExtractor.java index 2bda33c..45ed61a 100644 --- a/api/src/main/java/ru/abstractmenus/api/ValueExtractor.java +++ b/api/src/main/java/ru/abstractmenus/api/ValueExtractor.java @@ -1,17 +1,57 @@ package ru.abstractmenus.api; /** - * Implementors of extractor accepts some - * object with placeholder and returns required data from this object + * Strategy for resolving placeholder keys against an arbitrary context object. + * + *

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

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

Example

+ * + *
{@code
+ * ValueExtractor locationExtractor = (obj, key) -> {
+ *     if (!(obj instanceof Location loc)) return null;
+ *     return switch (key) {
+ *         case "world" -> loc.getWorld().getName();
+ *         case "x"     -> String.valueOf(loc.getBlockX());
+ *         case "y"     -> String.valueOf(loc.getBlockY());
+ *         case "z"     -> String.valueOf(loc.getBlockZ());
+ *         default      -> null;
+ *     };
+ * };
+ * }
+ * + * @see Activator#getValueExtractor() + * @see Catalog#extractor() */ public interface ValueExtractor { /** - * Get some data from given object by placeholder. - * You should cast object to required type first - * @param obj Any object - * @param placeholder Placeholder key. Example: "location_x" - * @return Resulting value in String format, or null + * Resolve {@code placeholder} against {@code obj} and return the String + * value that should be substituted in the rendered text. + * + *

Return {@code null} when the extractor doesn't recognize the key or + * when {@code obj} is not of a type this extractor handles — + * AbstractMenus will then consult the next extractor in the chain. + * + * @param obj the context object; narrow with {@code instanceof} + * before dereferencing + * @param placeholder the placeholder key (no surrounding {@code %}); e.g. + * {@code "location_x"} or {@code "name"} + * @return the resolved value as a {@link String}, or {@code null} if this + * extractor cannot resolve the key against {@code obj} + * + * @implNote Called on the main server thread during menu rendering — + * keep the body cheap and side-effect free. */ String extract(Object obj, String placeholder); From 58457131a1a39243eb12e76aeda0a9af2c7b2219 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Thu, 23 Apr 2026 11:45:53 +0500 Subject: [PATCH 60/71] docs(api): rich javadoc on Menu/Item/ItemProperty/Slot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Matches EconomyHandler / TypeRegistry style — class-level overview, addon-impl example where applicable, HOCON usage snippets, per-method @param / @return / @implNote. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../ru/abstractmenus/api/inventory/Item.java | 115 ++++++-- .../api/inventory/ItemProperty.java | 138 ++++++++-- .../ru/abstractmenus/api/inventory/Menu.java | 252 +++++++++++++++--- .../ru/abstractmenus/api/inventory/Slot.java | 70 ++++- 4 files changed, 490 insertions(+), 85 deletions(-) diff --git a/api/src/main/java/ru/abstractmenus/api/inventory/Item.java b/api/src/main/java/ru/abstractmenus/api/inventory/Item.java index dee5a0e..b59eb5d 100644 --- a/api/src/main/java/ru/abstractmenus/api/inventory/Item.java +++ b/api/src/main/java/ru/abstractmenus/api/inventory/Item.java @@ -3,58 +3,129 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; -import java.util.*; +import java.util.Map; /** - * Represent buildable ItemStack + * A single declared slot-filler inside a {@link Menu} — the bridge between + * a HOCON item block and the Bukkit {@link ItemStack} shown to a viewer. + * + *

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

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

Menu usage

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

Threading

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

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

Used by serializers when re-loading an item from config. Normal code + * should prefer {@link #addProperty(String, ItemProperty)} / + * {@link #removeProperty(String)} to avoid wiping rules-relevant state. + * + * @param properties the new property map; never {@code null} */ void setProperties(Map properties); /** - * Remove property from item - * @param key Key (name) of the property. This key you specify in item block in menu file - * @return Removed property or null if property with specified key is not assigned to item + * Detaches a property by key. + * + * @param key HOCON-visible property name + * @return the removed property, or {@code null} if none was attached under + * that key */ ItemProperty removeProperty(String key); /** - * Check is this item similar to som ItemStack - * @param item Bukkit ItemStack - * @param player Player to replace placeholders - * @return true if this item similar to provided or false otherwise + * Checks whether a raw {@link ItemStack} matches this item's declaration. + * + *

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

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

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

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

Example — a {@code glow} property

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

Menu usage

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

Threading

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

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

Return {@code true} when your property only mutates meta fields + * (display name, lore, flags, enchants) and lets the caller commit the + * meta for you. Return {@code false} when you need to call + * {@link ItemStack#setItemMeta(ItemMeta)} yourself — typically + * because you swap to a different meta type or tweak the stack in ways + * the shared commit would overwrite. * - * Return true if you make simple properties which modify only ItemMeta - * and doesn't touches ItemStack to assign it manually. + * @return {@code true} if the framework should call + * {@code item.setItemMeta(meta)} on your behalf after + * {@code apply} returns * - * If you set it for true and set item meta manually - * inside {@link ItemProperty#apply} method, then all changes won't be saved. - * @return true if allow or false otherwise + * @apiNote Returning {@code true} and also calling + * {@code item.setItemMeta(...)} manually inside {@code apply} + * leads to lost writes — the framework's follow-up + * {@code setItemMeta} overwrites yours. Pick one. */ boolean isApplyMeta(); /** - * Apply property to ItemStack. - * @param item Source ItemStack. - * @param meta Current meta of this item. - * @param player Player for who this item builds. - * @param menu Menu which cause item building. + * Applies this property to the in-flight item stack. + * + *

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

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

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

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

Opening a menu programmatically

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

Menu usage

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

Threading

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

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

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

Semantics depend on the firing {@link Activator}: a + * {@code PlayerInteractEvent} for {@code interact}, an {@code Entity} for + * {@code npcClick}, a {@code Block} for {@code blockClick}, and so on. + * Used by {@code ValueExtractor}s to resolve context-scoped placeholders. + * + * @return the activation context wrapped in an {@link Optional}, empty for + * programmatic opens + * + * @see ru.abstractmenus.api.ValueExtractor */ Optional getContext(); /** - * Get item in specified slot. This method works with already displayed items - * @param slot Inventory slot index - * @return Item object or null if not any items in specified slot + * Looks up an already-rendered item at the given raw inventory slot. + * + *

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

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

Only takes effect for subsequent {@link #open(Player)} calls — + * already-open viewers keep the old inventory until they close and reopen. + * + * @param size new size; must be a multiple of {@code 9} and within Bukkit's + * inventory limits */ void setSize(int size); /** - * Open menu for required player. Do not use this method to open menu. Always use AbstractMenusPlugin instance (wee wiki) - * @param player Required player - * @return true if menu opened or false otherwise (for example, if player doesn't match rules) + * Opens this menu for {@code player}, evaluating top-level rules first. + * + *

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

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

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

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

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

Shorthand for {@code close(player, true)}. + * + * @param player the viewer */ - default void close(Player player){ + default void close(Player player) { close(player, true); } /** - * Close menu for player - * @param player Who opened menu - * @param closeInventory Is inventory must be closed + * Releases per-viewer state and optionally closes the open inventory. + * + *

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

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

Menus are loaded once from disk and cloned per open session so + * per-viewer state (overridden items, generated layouts) doesn't leak + * between players. + * + * @return an independent copy of this menu */ Menu clone(); } diff --git a/api/src/main/java/ru/abstractmenus/api/inventory/Slot.java b/api/src/main/java/ru/abstractmenus/api/inventory/Slot.java index d8203d4..24a822f 100644 --- a/api/src/main/java/ru/abstractmenus/api/inventory/Slot.java +++ b/api/src/main/java/ru/abstractmenus/api/inventory/Slot.java @@ -3,15 +3,75 @@ import java.util.function.Consumer; /** - * Inventory slot representation. - * Implementors of this interface returns one or multiple slots. - * For example, range slot type returns several slots between x-y inclusive + * An addressable set of one or more inventory slot indices inside a + * {@link Menu}. + * + *

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

Example — iterating every slot an item occupies

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

Menu usage

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

Single-index variants invoke the callback once; ranges and matrices + * invoke it once per matched cell. Indices are zero-based and bounded by + * the owning menu's {@link Menu#getSize()}; callers are responsible for + * clamping against the actual {@link org.bukkit.inventory.Inventory} size + * if the menu was resized after parsing. + * + * @param indexCb consumer invoked once per resolved index; never + * {@code null} + * + * @implNote Implementations must not hold thread state between + * invocations — the callback may be invoked synchronously + * from multiple render passes on the main server thread. */ void getSlots(Consumer indexCb); From efc1c7fba2aa24e05a426964463fd7bb5628e572 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Thu, 23 Apr 2026 11:49:15 +0500 Subject: [PATCH 61/71] docs(api): rich javadoc on Slot{Index,Pos,Range,Matrix} variants Co-Authored-By: Claude Opus 4.7 (1M context) --- .../api/inventory/slot/SlotIndex.java | 59 +++++++++++++++++-- .../api/inventory/slot/SlotMatrix.java | 53 +++++++++++++++-- .../api/inventory/slot/SlotPos.java | 48 +++++++++++++-- .../api/inventory/slot/SlotRange.java | 49 +++++++++++++-- 4 files changed, 191 insertions(+), 18 deletions(-) diff --git a/api/src/main/java/ru/abstractmenus/api/inventory/slot/SlotIndex.java b/api/src/main/java/ru/abstractmenus/api/inventory/slot/SlotIndex.java index 309df45..57d620d 100644 --- a/api/src/main/java/ru/abstractmenus/api/inventory/slot/SlotIndex.java +++ b/api/src/main/java/ru/abstractmenus/api/inventory/slot/SlotIndex.java @@ -5,29 +5,78 @@ import java.util.function.Consumer; /** - * Slot defined just by index - * # Config example - * slot: 0 - * slot: 51 - * slot: -1 (auto insert in first free slot) + * A {@link ru.abstractmenus.api.inventory.Slot} variant that addresses a + * single inventory cell by its absolute zero-based index (0 … 53). + * + *

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

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

HOCON examples

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

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

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

HOCON example

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

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

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

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

HOCON examples

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

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

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

HOCON examples

+ * + *
{@code
+ * # fill the first row (slots 0-8)
+ * slot: "0-8"
+ *
+ * # border band across slots 10-16
+ * slot: "10-16"
+ * }
+ * + * @see SlotIndex + * @see SlotPos + * @see SlotMatrix + * @see ru.abstractmenus.api.inventory.Slot */ public class SlotRange implements Slot { private final int min; private final int max; + /** + * Creates a {@code SlotRange} spanning [{@code min}, {@code max}] inclusive. + * + * @param min lower bound, zero-based; clamped to {@code 0} if negative + * @param max upper bound, zero-based, inclusive; must be ≥ {@code min} + * after clamping + */ public SlotRange(int min, int max) { this.min = Math.max(min, 0); this.max = max; } + /** + * Iterates every index in the range [{@code min}, {@code max}] inclusive + * and passes each to {@code indexCb}. + * + * @param indexCb consumer invoked once per slot in ascending order; + * never {@code null} + * + * @implNote The callback is invoked {@code max - min + 1} times in + * strictly ascending order, starting from the clamped + * {@code min} value. + */ @Override public void getSlots(Consumer indexCb) { for (int i = min; i <= max; i++) { From a5d577f9e2389ad2a44787ebefdb5563b12218a7 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Thu, 23 Apr 2026 11:52:50 +0500 Subject: [PATCH 62/71] docs(api): rich javadoc on Var/VarBuilder/VariableManager Co-Authored-By: Claude Opus 4.7 (1M context) --- .../ru/abstractmenus/api/variables/Var.java | 152 ++++++++++++---- .../api/variables/VarBuilder.java | 98 ++++++++-- .../api/variables/VariableManager.java | 169 +++++++++++++++--- 3 files changed, 342 insertions(+), 77 deletions(-) diff --git a/api/src/main/java/ru/abstractmenus/api/variables/Var.java b/api/src/main/java/ru/abstractmenus/api/variables/Var.java index adf1c48..fc0bd99 100644 --- a/api/src/main/java/ru/abstractmenus/api/variables/Var.java +++ b/api/src/main/java/ru/abstractmenus/api/variables/Var.java @@ -1,81 +1,171 @@ package ru.abstractmenus.api.variables; /** - * Variable data. Variables are immutable. - * If you need to change and save it again, you need to clone it first. + * Immutable snapshot of a single AbstractMenus variable — a named string + * value with an optional expiry timestamp. + * + *

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

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

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

Example — increment a personal counter

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

Menu usage

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

Threading

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

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

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

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

Example — a 10-minute cooldown variable

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

Threading

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

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

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

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

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

Obtaining the manager

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

Example — daily-reward gate

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

Menu usage

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

Threading

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

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

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

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

Removes the entry from the cache, deletes the row from SQLite and, if + * cross-server sync is enabled, broadcasts the deletion to sibling + * servers. Unknown combinations are silently ignored. + * + * @param username the owning player's name; compared case-insensitively + * @param name the variable name; compared case-insensitively + * @apiNote Performs IO on the calling thread — consider scheduling + * off the main thread when deleting many variables in a row. */ void deletePersonal(String username, String name); /** - * Create builder for variable - * @return New variable builder + * Create an empty {@link VarBuilder} for a brand-new variable. To derive + * one from an existing variable instead, call {@link Var#toBuilder()}. + * + * @return a fresh builder; never {@code null} */ VarBuilder createBuilder(); From 0427b5bb0782fc77440c563378965ef542b3d012 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Thu, 23 Apr 2026 11:55:14 +0500 Subject: [PATCH 63/71] =?UTF-8?q?docs(api):=20rich=20javadoc=20on=20Colors?= =?UTF-8?q?=20=E2=80=94=20legacy/hex=20code=20conversion=20utility?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 (1M context) --- .../ru/abstractmenus/api/text/Colors.java | 160 ++++++++++++++++-- 1 file changed, 148 insertions(+), 12 deletions(-) diff --git a/api/src/main/java/ru/abstractmenus/api/text/Colors.java b/api/src/main/java/ru/abstractmenus/api/text/Colors.java index 6ce3830..c711f4e 100644 --- a/api/src/main/java/ru/abstractmenus/api/text/Colors.java +++ b/api/src/main/java/ru/abstractmenus/api/text/Colors.java @@ -7,7 +7,50 @@ import java.util.regex.Pattern; /** - * Util to easy color codes replacing + * Utility for converting user-facing color notation to Minecraft section-code strings. + * + *

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

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

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

Example

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

Performance

+ * + *

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

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

The decision tree: + *

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

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

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

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

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

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

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

Hex tokens are matched by {@link #PATTERN} and replaced with the BungeeCord + * section-sign sequence produced by + * {@code net.md_5.bungee.api.ChatColor#of(String)}. After all tokens are expanded, + * {@link ChatColor#translateAlternateColorCodes} handles the remaining {@code &X} + * codes. + * + * @implNote Each hex replacement rebuilds the full string and resets the + * {@link Matcher}, giving {@code O(n × k)} allocation where + * {@code n} is the string length and {@code k} is the number of hex + * tokens. Strings should be colorized once at load time rather than on + * every display to keep hot paths allocation-free. + */ private static class RgbReplacer implements Replacer { + /** + * Matches hex color tokens in the form {@code <#RRGGBB>}, case-insensitive. + * Captures exactly six hexadecimal digits between {@code <#} and {@code >}. + */ private static final Pattern PATTERN = Pattern.compile("<#([A-Fa-f0-9]){6}>"); @Override From 1850d233374df498b206c06c699200c0dfbcf048 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Thu, 23 Apr 2026 11:59:55 +0500 Subject: [PATCH 64/71] edit gitignore Signed-off-by: Stanislav Panchenko --- .gitignore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 6c1a880..9bb1ddf 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,4 @@ .idea build .DS_Store -logs -PR_DESCRIPTION.md +logs \ No newline at end of file From 4f6cb9a8d3cc17a0525b1ad859b7415f8bec7664 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Thu, 23 Apr 2026 13:28:54 +0500 Subject: [PATCH 65/71] small changes Signed-off-by: Stanislav Panchenko --- .../ru/abstractmenus/addon/AddonManager.java | 74 +++++++++--------- .../ru/abstractmenus/addon/AddonStatus.java | 2 +- .../ru/abstractmenus/addon/LoadedAddon.java | 30 +++++--- .../api/AbstractMenusApiImpl.java | 77 ++++++++++++++----- .../commands/am/CommandAddons.java | 22 +++--- .../addon/AddonManagerIntegrationTest.java | 6 +- 6 files changed, 126 insertions(+), 85 deletions(-) diff --git a/plugin/src/main/java/ru/abstractmenus/addon/AddonManager.java b/plugin/src/main/java/ru/abstractmenus/addon/AddonManager.java index 1285cc9..8420116 100644 --- a/plugin/src/main/java/ru/abstractmenus/addon/AddonManager.java +++ b/plugin/src/main/java/ru/abstractmenus/addon/AddonManager.java @@ -41,7 +41,7 @@ public AddonManager(AbstractMenus plugin, AbstractMenusApi api) { * tests). Any addon with a non-empty {@code pluginDependencies} will fail * under this constructor. */ - AddonManager(java.nio.file.Path addonsDir, ru.abstractmenus.api.AbstractMenusApi api) { + AddonManager(Path addonsDir, AbstractMenusApi api) { this.plugin = null; this.api = api; this.addonsDir = addonsDir; @@ -66,7 +66,7 @@ public void loadAll() { if (plugin != null) { var pluginManager = plugin.getServer().getPluginManager(); for (LoadedAddon la : pending.values()) { - AddonConf c = la.conf(); + AddonConf c = la.getConf(); boolean missing = false; for (String dep : c.pluginDependencies()) { if (pluginManager.getPlugin(dep) == null) { @@ -92,7 +92,7 @@ public void loadAll() { // Sort by addon-level dependencies. Map> depGraph = new LinkedHashMap<>(); for (var e : byName.entrySet()) { - List deps = e.getValue().conf().addonDependencies().stream() + List deps = e.getValue().getConf().addonDependencies().stream() .map(String::toLowerCase).toList(); depGraph.put(e.getKey(), deps); } @@ -103,7 +103,7 @@ public void loadAll() { Logger.severe("Addon dependency graph error: " + ex.getMessage()); for (var la : byName.values()) { la.markFailed(ex); - addons.put(la.conf().name().toLowerCase(), la); + addons.put(la.getConf().name().toLowerCase(), la); } return; } @@ -113,9 +113,9 @@ public void loadAll() { LoadedAddon la = byName.get(k); try { la.setExtension(instantiate(la)); - la.extension().onLoad(api); + la.getExtension().onLoad(api); } catch (Throwable t) { - Logger.severe("Addon " + la.conf().name() + " failed in onLoad: " + t); + Logger.severe("Addon " + la.getConf().name() + " failed in onLoad: " + t); t.printStackTrace(); la.markFailed(t); } @@ -124,20 +124,20 @@ public void loadAll() { // Stage 2: onEnable in dependency order. for (String k : order) { LoadedAddon la = byName.get(k); - if (la.status() == AddonStatus.FAILED) { + if (la.getStatus() == AddonStatus.FAILED) { addons.put(k, la); continue; } try { - la.extension().onEnable(api); + la.getExtension().onEnable(api); la.markEnabled(); - Logger.info("Enabled addon: " + la.conf().name() - + " v" + la.conf().version() - + (la.conf().targetApiVersion() == null + Logger.info("Enabled addon: " + la.getConf().name() + + " v" + la.getConf().version() + + (la.getConf().targetApiVersion() == null ? "" - : " (built against API " + la.conf().targetApiVersion() + ")")); + : " (built against API " + la.getConf().targetApiVersion() + ")")); } catch (Throwable t) { - Logger.severe("Addon " + la.conf().name() + " failed in onEnable: " + t); + Logger.severe("Addon " + la.getConf().name() + " failed in onEnable: " + t); t.printStackTrace(); la.markFailed(t); rollbackRegistrations(la); @@ -151,7 +151,7 @@ public void loadAll() { * MenuExtension. */ private ru.abstractmenus.api.MenuExtension instantiate(LoadedAddon la) throws Exception { - Class main = la.classLoader().loadClass(la.conf().main()); + Class main = la.getClassLoader().loadClass(la.getConf().main()); if (!ru.abstractmenus.api.MenuExtension.class.isAssignableFrom(main)) { throw new IllegalStateException("main class " + main.getName() + " does not implement MenuExtension"); @@ -161,12 +161,12 @@ private ru.abstractmenus.api.MenuExtension instantiate(LoadedAddon la) throws Ex /** Strip any type registrations the failed addon managed to make. */ private void rollbackRegistrations(LoadedAddon la) { - if (la.extension() == null) return; - api.actions().unregisterAll(la.extension()); - api.rules().unregisterAll(la.extension()); - api.activators().unregisterAll(la.extension()); - api.itemProperties().unregisterAll(la.extension()); - api.catalogs().unregisterAll(la.extension()); + if (la.getExtension() == null) return; + api.actions().unregisterAll(la.getExtension()); + api.rules().unregisterAll(la.getExtension()); + api.activators().unregisterAll(la.getExtension()); + api.itemProperties().unregisterAll(la.getExtension()); + api.catalogs().unregisterAll(la.getExtension()); } /** @@ -179,20 +179,20 @@ public void unloadAll() { java.util.Collections.reverse(reversed); for (LoadedAddon la : reversed) { try { - if (la.status() == AddonStatus.ENABLED && la.extension() != null) { - la.extension().onDisable(api); + if (la.getStatus() == AddonStatus.ENABLED && la.getExtension() != null) { + la.getExtension().onDisable(api); } rollbackRegistrations(la); la.markDisabled(); } catch (Throwable t) { - Logger.severe("Addon " + la.conf().name() + " failed in onDisable: " + t); + Logger.severe("Addon " + la.getConf().name() + " failed in onDisable: " + t); t.printStackTrace(); // Don't let one bad disable block the others. } try { - la.classLoader().close(); + la.getClassLoader().close(); } catch (Exception e) { - Logger.warning("Addon " + la.conf().name() + " classloader close failed: " + e); + Logger.warning("Addon " + la.getConf().name() + " classloader close failed: " + e); } } addons.clear(); @@ -213,15 +213,15 @@ public Optional reload(String name) { // Disable + unhook current instance. try { - if (existing.status() == AddonStatus.ENABLED && existing.extension() != null) { - existing.extension().onDisable(api); + if (existing.getStatus() == AddonStatus.ENABLED && existing.getExtension() != null) { + existing.getExtension().onDisable(api); } rollbackRegistrations(existing); } catch (Throwable t) { - Logger.warning("Addon " + existing.conf().name() + Logger.warning("Addon " + existing.getConf().name() + " failed in onDisable during reload: " + t); } - try { existing.classLoader().close(); } catch (Exception ignored) {} + try { existing.getClassLoader().close(); } catch (Exception ignored) {} addons.remove(key); // Re-discover: find the jar whose addon.conf name matches. @@ -244,17 +244,17 @@ public Optional reload(String name) { // enabled (they were, before this reload). try { fresh.setExtension(instantiate(fresh)); - fresh.extension().onLoad(api); - fresh.extension().onEnable(api); + fresh.getExtension().onLoad(api); + fresh.getExtension().onEnable(api); fresh.markEnabled(); - addons.put(fresh.conf().name().toLowerCase(), fresh); - Logger.info("Reloaded addon: " + fresh.conf().name() + " v" + fresh.conf().version()); + addons.put(fresh.getConf().name().toLowerCase(), fresh); + Logger.info("Reloaded addon: " + fresh.getConf().name() + " v" + fresh.getConf().version()); } catch (Throwable t) { Logger.severe("Addon " + name + " failed during reload: " + t); t.printStackTrace(); fresh.markFailed(t); rollbackRegistrations(fresh); - addons.put(fresh.conf().name().toLowerCase(), fresh); + addons.put(fresh.getConf().name().toLowerCase(), fresh); } return Optional.of(fresh); @@ -313,11 +313,11 @@ Map discover() { for (Path jar : stream) { try { LoadedAddon addon = readAddonJar(jar); - String key = addon.conf().name().toLowerCase(); + String key = addon.getConf().name().toLowerCase(); if (pending.containsKey(key)) { - Logger.warning("Duplicate addon name '" + addon.conf().name() + Logger.warning("Duplicate addon name '" + addon.getConf().name() + "' — ignoring " + jar.getFileName()); - try { addon.classLoader().close(); } catch (Exception ignored) {} + try { addon.getClassLoader().close(); } catch (Exception ignored) {} continue; } pending.put(key, addon); diff --git a/plugin/src/main/java/ru/abstractmenus/addon/AddonStatus.java b/plugin/src/main/java/ru/abstractmenus/addon/AddonStatus.java index d49d661..b33add0 100644 --- a/plugin/src/main/java/ru/abstractmenus/addon/AddonStatus.java +++ b/plugin/src/main/java/ru/abstractmenus/addon/AddonStatus.java @@ -8,6 +8,6 @@ public enum AddonStatus { ENABLED, /** Cleanly disabled — {@code onDisable} ran and registry entries were cleared. */ DISABLED, - /** Failed to load or enable — see {@link LoadedAddon#error()} for the cause. */ + /** Failed to load or enable — see {@link LoadedAddon#getError()} for the cause. */ FAILED } diff --git a/plugin/src/main/java/ru/abstractmenus/addon/LoadedAddon.java b/plugin/src/main/java/ru/abstractmenus/addon/LoadedAddon.java index 15920d8..642acbc 100644 --- a/plugin/src/main/java/ru/abstractmenus/addon/LoadedAddon.java +++ b/plugin/src/main/java/ru/abstractmenus/addon/LoadedAddon.java @@ -1,5 +1,8 @@ package ru.abstractmenus.addon; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; import ru.abstractmenus.api.MenuExtension; /** @@ -10,27 +13,30 @@ *

Instances are created by {@code AddonManager} during discovery and * transition through enable → disable / failed states. */ +@RequiredArgsConstructor +@Getter public final class LoadedAddon { + private final AddonConf conf; private final AddonClassLoader classLoader; + @Setter private MenuExtension extension; // null until onLoad completes private AddonStatus status = AddonStatus.PENDING; private Throwable error; // non-null iff status == FAILED - public LoadedAddon(AddonConf conf, AddonClassLoader classLoader) { - this.conf = conf; - this.classLoader = classLoader; + public void markEnabled() { + this.status = AddonStatus.ENABLED; + this.error = null; } - public AddonConf conf() { return conf; } - public AddonClassLoader classLoader() { return classLoader; } - public MenuExtension extension() { return extension; } - public AddonStatus status() { return status; } - public Throwable error() { return error; } + public void markDisabled() { + this.status = AddonStatus.DISABLED; + this.error = null; + } - public void setExtension(MenuExtension e) { this.extension = e; } - public void markEnabled() { this.status = AddonStatus.ENABLED; this.error = null; } - public void markDisabled() { this.status = AddonStatus.DISABLED; this.error = null; } - public void markFailed(Throwable t) { this.status = AddonStatus.FAILED; this.error = t; } + public void markFailed(Throwable t) { + this.status = AddonStatus.FAILED; + this.error = t; + } } diff --git a/plugin/src/main/java/ru/abstractmenus/api/AbstractMenusApiImpl.java b/plugin/src/main/java/ru/abstractmenus/api/AbstractMenusApiImpl.java index 4fdcbda..922ab3a 100644 --- a/plugin/src/main/java/ru/abstractmenus/api/AbstractMenusApiImpl.java +++ b/plugin/src/main/java/ru/abstractmenus/api/AbstractMenusApiImpl.java @@ -20,39 +20,74 @@ public final class AbstractMenusApiImpl implements AbstractMenusApi { private final AbstractMenus plugin; private final NodeSerializers serializers = NodeSerializers.defaults(); - private final TypeRegistry actions; - private final TypeRegistry rules; - private final TypeRegistry activators; - private final TypeRegistry itemProperties; - private final TypeRegistry> catalogs; - private final ProviderRegistry providers; + private final TypeRegistry actions; + private final TypeRegistry rules; + private final TypeRegistry activators; + private final TypeRegistry itemProperties; + private final TypeRegistry> catalogs; + private final ProviderRegistry providers; public AbstractMenusApiImpl(AbstractMenus plugin) { this.plugin = plugin; - this.actions = new TypeRegistryImpl<>(serializers); - this.rules = new TypeRegistryImpl<>(serializers); - this.activators = new TypeRegistryImpl<>(serializers); + this.actions = new TypeRegistryImpl<>(serializers); + this.rules = new TypeRegistryImpl<>(serializers); + this.activators = new TypeRegistryImpl<>(serializers); this.itemProperties = new TypeRegistryImpl<>(serializers); - this.catalogs = new TypeRegistryImpl<>(serializers); + this.catalogs = new TypeRegistryImpl<>(serializers); ProviderRegistryImpl providerImpl = new ProviderRegistryImpl(); providerImpl.setConfigDefaults(kind -> plugin.getMainConfig().providerDefault(kind)); - this.providers = providerImpl; + this.providers = providerImpl; } - @Override public TypeRegistry actions() { return actions; } - @Override public TypeRegistry rules() { return rules; } - @Override public TypeRegistry activators() { return activators; } - @Override public TypeRegistry itemProperties() { return itemProperties; } - @Override public TypeRegistry> catalogs() { return catalogs; } - @Override public ProviderRegistry providers() { return providers; } + @Override + public TypeRegistry actions() { + return actions; + } - @Override public NodeSerializers serializers() { return serializers; } + @Override + public TypeRegistry rules() { + return rules; + } - @Override public VariableManager variables() { return plugin.getVariableManager(); } + @Override + public TypeRegistry activators() { + return activators; + } - @Override public Plugin getPlugin() { return plugin; } + @Override + public TypeRegistry itemProperties() { + return itemProperties; + } + + @Override + public TypeRegistry> catalogs() { + return catalogs; + } - @Override public void loadMenus() { plugin.loadMenus(); } + @Override + public ProviderRegistry providers() { + return providers; + } + + @Override + public NodeSerializers serializers() { + return serializers; + } + + @Override + public VariableManager variables() { + return plugin.getVariableManager(); + } + + @Override + public Plugin getPlugin() { + return plugin; + } + + @Override + public void loadMenus() { + plugin.loadMenus(); + } @Override public void openMenu(Activator activator, Object ctx, Player player, Menu menu) { diff --git a/plugin/src/main/java/ru/abstractmenus/commands/am/CommandAddons.java b/plugin/src/main/java/ru/abstractmenus/commands/am/CommandAddons.java index 518c480..95c7a9b 100644 --- a/plugin/src/main/java/ru/abstractmenus/commands/am/CommandAddons.java +++ b/plugin/src/main/java/ru/abstractmenus/commands/am/CommandAddons.java @@ -48,15 +48,15 @@ private void list(CommandSender sender, AddonManager am) { } sender.sendMessage(Colors.of("&e&lAddons (" + addons.size() + "):")); for (LoadedAddon la : addons) { - String color = switch (la.status()) { + String color = switch (la.getStatus()) { case ENABLED -> "&a"; case DISABLED -> "&7"; case FAILED -> "&c"; case PENDING -> "&e"; }; - sender.sendMessage(Colors.of(color + " " + la.conf().name() - + " &8v" + la.conf().version() - + " &7[" + la.status() + "]")); + sender.sendMessage(Colors.of(color + " " + la.getConf().name() + + " &8v" + la.getConf().version() + + " &7[" + la.getStatus() + "]")); } } @@ -72,11 +72,11 @@ private void reload(CommandSender sender, AddonManager am, String[] args) { return; } LoadedAddon la = result.get(); - if (la.status() == AddonStatus.ENABLED) { - sender.sendMessage(Colors.of("&aReloaded " + la.conf().name() + ".")); + if (la.getStatus() == AddonStatus.ENABLED) { + sender.sendMessage(Colors.of("&aReloaded " + la.getConf().name() + ".")); } else { sender.sendMessage(Colors.of("&cReload failed: " - + (la.error() == null ? "unknown error" : la.error().getMessage()))); + + (la.getError() == null ? "unknown error" : la.getError().getMessage()))); } } @@ -91,9 +91,9 @@ private void info(CommandSender sender, AddonManager am, String[] args) { return; } LoadedAddon la = opt.get(); - var c = la.conf(); + var c = la.getConf(); sender.sendMessage(Colors.of("&e&l" + c.name() + " &7v" + c.version())); - sender.sendMessage(Colors.of("&7 status: &f" + la.status())); + sender.sendMessage(Colors.of("&7 status: &f" + la.getStatus())); if (!c.authors().isEmpty()) { sender.sendMessage(Colors.of("&7 authors: &f" + String.join(", ", c.authors()))); } @@ -111,8 +111,8 @@ private void info(CommandSender sender, AddonManager am, String[] args) { sender.sendMessage(Colors.of("&7 pluginDependencies: &f" + String.join(", ", c.pluginDependencies()))); } - if (la.status() == AddonStatus.FAILED && la.error() != null) { - sender.sendMessage(Colors.of("&7 error: &c" + la.error().getMessage())); + if (la.getStatus() == AddonStatus.FAILED && la.getError() != null) { + sender.sendMessage(Colors.of("&7 error: &c" + la.getError().getMessage())); } } } diff --git a/plugin/src/test/java/ru/abstractmenus/addon/AddonManagerIntegrationTest.java b/plugin/src/test/java/ru/abstractmenus/addon/AddonManagerIntegrationTest.java index 449d37a..e021a5f 100644 --- a/plugin/src/test/java/ru/abstractmenus/addon/AddonManagerIntegrationTest.java +++ b/plugin/src/test/java/ru/abstractmenus/addon/AddonManagerIntegrationTest.java @@ -117,10 +117,10 @@ void loadAll_readsJar_enablesAddon_registersAction(@TempDir Path serverRoot) thr // Verify: exactly one addon is loaded, with ENABLED status. assertEquals(1, manager.loaded().size()); LoadedAddon loaded = manager.loaded().iterator().next(); - assertEquals("TestAddon", loaded.conf().name()); - assertEquals(AddonStatus.ENABLED, loaded.status(), + assertEquals("TestAddon", loaded.getConf().name()); + assertEquals(AddonStatus.ENABLED, loaded.getStatus(), "addon must reach ENABLED status (error: " - + (loaded.error() == null ? "none" : loaded.error()) + ")"); + + (loaded.getError() == null ? "none" : loaded.getError()) + ")"); // Verify: the action is registered. assertNotNull(api.actions().get("testPing"), From a33a405c27886f3b91159c357ffdb62bb09faef7 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Mon, 27 Apr 2026 13:06:57 +0500 Subject: [PATCH 66/71] feat(addons): /am addons load|rescan + tab completion AddonManager: - loadOne(name): load a single addon by addon.conf name from disk, respecting pluginDependencies and addonDependencies. Used by both /am addons load and the rescan path. - rescan(): scan addons/ and load anything not yet known. Existing addons are left untouched - use reload(name) to rebuild them. - availableNotLoaded(): names of addons present on disk but not yet loaded. Powers tab completion for /am addons load . Cost is one jar open + HOCON parse per .jar; acceptable at typical scale. - enableSingle(): private helper extracted from loadAll, owns the pluginDeps + addonDeps + onLoad + onEnable sequence for one addon. CommandAddons: - new subcommands: load and rescan - override tabComplete to suggest: - args[0]: list/reload/info/load/rescan filtered by prefix - args[1] for reload|info: loaded-addon names - args[1] for load: availableNotLoaded() names Command base: - now also implements TabCompleter - default tabComplete: subcommand-key matching at args[0], then recursive drill into the matched subcommand for args[1..] AbstractMenus#registerCommands: - wire setTabCompleter alongside setExecutor for /am, /var, /varp. /var and /varp inherit the default subcommand-key completion. --- .../java/ru/abstractmenus/AbstractMenus.java | 14 +- .../ru/abstractmenus/addon/AddonManager.java | 136 ++++++++++++++++++ .../commands/AbstractMenuCommand.java | 2 +- .../ru/abstractmenus/commands/Command.java | 43 +++++- .../commands/am/CommandAddons.java | 104 +++++++++++++- 5 files changed, 287 insertions(+), 12 deletions(-) diff --git a/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java b/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java index cc34cd0..9828956 100644 --- a/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java +++ b/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java @@ -234,9 +234,17 @@ private void registerCommands(MainConfig config) { .addSub("mul", new VarpMul()) .addSub("div", new VarpDiv()); - Objects.requireNonNull(getServer().getPluginCommand("am")).setExecutor(am); - Objects.requireNonNull(getServer().getPluginCommand("var")).setExecutor(var); - Objects.requireNonNull(getServer().getPluginCommand("varp")).setExecutor(varp); + var amCmd = Objects.requireNonNull(getServer().getPluginCommand("am")); + amCmd.setExecutor(am); + amCmd.setTabCompleter(am); + + var varCmd = Objects.requireNonNull(getServer().getPluginCommand("var")); + varCmd.setExecutor(var); + varCmd.setTabCompleter(var); + + var varpCmd = Objects.requireNonNull(getServer().getPluginCommand("varp")); + varpCmd.setExecutor(varp); + varpCmd.setTabCompleter(varp); } private void disablePlugin() { diff --git a/plugin/src/main/java/ru/abstractmenus/addon/AddonManager.java b/plugin/src/main/java/ru/abstractmenus/addon/AddonManager.java index 8420116..8747ffe 100644 --- a/plugin/src/main/java/ru/abstractmenus/addon/AddonManager.java +++ b/plugin/src/main/java/ru/abstractmenus/addon/AddonManager.java @@ -5,6 +5,7 @@ import ru.abstractmenus.api.Logger; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; @@ -332,6 +333,141 @@ Map discover() { return pending; } + /** + * Load a single addon by its addon.conf {@code name} from the addons + * directory. Useful when the operator drops one new jar at runtime + * and runs {@code /am addons load } - no need to bounce the + * server to discover it. + * + *

Returns {@code Optional.empty()} if no jar with a matching + * {@code addon.conf name} is found, or if an addon with that name is + * already in the loaded map. Otherwise returns the {@link LoadedAddon} + * (which may be ENABLED or FAILED depending on what happened). + * + * @param name the addon-conf {@code name}, case-insensitive + */ + public Optional loadOne(String name) { + if (addons.containsKey(name.toLowerCase())) { + return Optional.empty(); + } + Path jar = findJarByName(name); + if (jar == null) return Optional.empty(); + + LoadedAddon la; + try { + la = readAddonJar(jar); + } catch (Exception e) { + Logger.severe("Addon " + name + " failed to parse: " + e.getMessage()); + return Optional.empty(); + } + enableSingle(la); + return Optional.of(la); + } + + /** + * Re-scan the addons directory and load every addon not already in + * the loaded map. Existing addons are left alone (their classloader + * is not rebuilt - use {@link #reload(String)} for that). Returns + * the list of addons that were attempted in this call (some may + * have ended up in FAILED state). + */ + public List rescan() { + List newlyLoaded = new ArrayList<>(); + Map discovered = discover(); + for (var entry : discovered.entrySet()) { + LoadedAddon la = entry.getValue(); + if (addons.containsKey(entry.getKey())) { + // Already loaded - drop the redundant classloader we just built. + try { la.getClassLoader().close(); } catch (Exception ignored) {} + continue; + } + enableSingle(la); + newlyLoaded.add(la); + } + return newlyLoaded; + } + + /** + * Return addon-conf {@code name}s found on disk under the addons + * directory but not yet loaded into memory. Used by tab completion + * for {@code /am addons load }. Cost is one jar open and one + * HOCON parse per .jar in the directory - acceptable at typical + * scale (1-20 addons), but be aware this is not free. + */ + public List availableNotLoaded() { + if (!java.nio.file.Files.isDirectory(addonsDir)) return List.of(); + List result = new ArrayList<>(); + try (var stream = java.nio.file.Files.newDirectoryStream(addonsDir, "*.jar")) { + for (Path jar : stream) { + try (var jf = new java.util.jar.JarFile(jar.toFile())) { + var entry = jf.getJarEntry("addon.conf"); + if (entry == null) continue; + String hocon = new String(jf.getInputStream(entry).readAllBytes(), + java.nio.charset.StandardCharsets.UTF_8); + AddonConf conf = AddonConf.parse(hocon); + if (!addons.containsKey(conf.name().toLowerCase())) { + result.add(conf.name()); + } + } catch (Exception ignored) { + // Malformed jar - skip silently, the operator already saw + // the warning at server-start discover() time. + } + } + } catch (Exception ignored) {} + return result; + } + + /** + * Verify Bukkit-side and addon-side dependencies, then run + * onLoad + onEnable. Installs the result into the loaded map + * (regardless of success or failure - failed addons stay visible + * in {@code /am addons list} so the operator can debug them). + */ + private void enableSingle(LoadedAddon la) { + String key = la.getConf().name().toLowerCase(); + + if (plugin != null) { + var pm = plugin.getServer().getPluginManager(); + for (String dep : la.getConf().pluginDependencies()) { + if (pm.getPlugin(dep) == null) { + String msg = "missing plugin dependency: " + dep; + Logger.warning("Addon " + la.getConf().name() + " " + msg); + la.markFailed(new IllegalStateException(msg)); + addons.put(key, la); + return; + } + } + } + + for (String dep : la.getConf().addonDependencies()) { + LoadedAddon depAddon = addons.get(dep.toLowerCase()); + if (depAddon == null || depAddon.getStatus() != AddonStatus.ENABLED) { + String msg = "missing or unhealthy addon dependency: " + dep; + Logger.warning("Addon " + la.getConf().name() + " " + msg); + la.markFailed(new IllegalStateException(msg)); + addons.put(key, la); + return; + } + } + + try { + la.setExtension(instantiate(la)); + la.getExtension().onLoad(api); + la.getExtension().onEnable(api); + la.markEnabled(); + Logger.info("Enabled addon: " + la.getConf().name() + + " v" + la.getConf().version() + + (la.getConf().targetApiVersion() == null ? "" + : " (built against API " + la.getConf().targetApiVersion() + ")")); + } catch (Throwable t) { + Logger.severe("Addon " + la.getConf().name() + " failed during enable: " + t); + t.printStackTrace(); + la.markFailed(t); + rollbackRegistrations(la); + } + addons.put(key, la); + } + /** * Read a single addon jar: extract {@code addon.conf}, parse it, build a * classloader. Throws if addon.conf is missing or malformed. diff --git a/plugin/src/main/java/ru/abstractmenus/commands/AbstractMenuCommand.java b/plugin/src/main/java/ru/abstractmenus/commands/AbstractMenuCommand.java index d8dd46e..60fc805 100644 --- a/plugin/src/main/java/ru/abstractmenus/commands/AbstractMenuCommand.java +++ b/plugin/src/main/java/ru/abstractmenus/commands/AbstractMenuCommand.java @@ -14,7 +14,7 @@ public AbstractMenuCommand(String permission) { Colors.of("&7/am reload &e- Reload all menu files. Also close all opened menus for player"), Colors.of("&7/am serve &e- Turn on/off automatic menu reloading if file changed"), Colors.of("&7/am version &e- show plugin version"), - Colors.of("&7/am addons list|reload|info &e- manage AM-loaded addons") + Colors.of("&7/am addons list|reload|info|load|rescan &e- manage AM-loaded addons") ); } diff --git a/plugin/src/main/java/ru/abstractmenus/commands/Command.java b/plugin/src/main/java/ru/abstractmenus/commands/Command.java index b5e4625..ce9a88f 100644 --- a/plugin/src/main/java/ru/abstractmenus/commands/Command.java +++ b/plugin/src/main/java/ru/abstractmenus/commands/Command.java @@ -3,14 +3,18 @@ import lombok.Getter; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Stack; -public abstract class Command implements CommandExecutor { +public abstract class Command implements CommandExecutor, TabCompleter { @Getter private String permission; @@ -96,4 +100,41 @@ private boolean checkPermission(CommandSender sender, Command command) { public abstract void execute(CommandSender sender, String[] args); + @Override + public @NotNull List onTabComplete(@NotNull CommandSender sender, @NotNull org.bukkit.command.Command cmd, @NotNull String alias, @NotNull String[] args) { + if (!checkPermission(sender, this)) return Collections.emptyList(); + return tabComplete(sender, args); + } + + /** + * Recursive tab-completion entry point. Default behaviour: if the + * user is typing the first arg, return registered subcommand keys + * matching the current prefix; if a subcommand has already been + * named, drill into it and forward {@code args[1..]}. Subclasses + * override this method to add custom completions for typed values + * (e.g. addon names, menu names, online players). + * + *

Suggestions are only returned for the *last* arg in {@code args} + * (the one Bukkit considers the "in-progress" token). + */ + public List tabComplete(CommandSender sender, String[] args) { + if (args.length == 0) return Collections.emptyList(); + if (args.length == 1) { + String prefix = args[0].toLowerCase(); + List result = new ArrayList<>(); + for (Map.Entry e : subCommands.entrySet()) { + if (!checkPermission(sender, e.getValue())) continue; + if (e.getKey().toLowerCase().startsWith(prefix)) { + result.add(e.getKey()); + } + } + Collections.sort(result); + return result; + } + Command sub = getSub(args[0]); + if (sub == null) return Collections.emptyList(); + if (!checkPermission(sender, sub)) return Collections.emptyList(); + return sub.tabComplete(sender, Arrays.copyOfRange(args, 1, args.length)); + } + } diff --git a/plugin/src/main/java/ru/abstractmenus/commands/am/CommandAddons.java b/plugin/src/main/java/ru/abstractmenus/commands/am/CommandAddons.java index 95c7a9b..54e4f57 100644 --- a/plugin/src/main/java/ru/abstractmenus/commands/am/CommandAddons.java +++ b/plugin/src/main/java/ru/abstractmenus/commands/am/CommandAddons.java @@ -8,14 +8,23 @@ import ru.abstractmenus.api.text.Colors; import ru.abstractmenus.commands.Command; -/** {@code /am addons [list|reload |info ]} */ +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** {@code /am addons [list|reload |info |load |rescan]} */ public class CommandAddons extends Command { + private static final List SUBCOMMANDS = + List.of("list", "reload", "info", "load", "rescan"); + public CommandAddons() { setUsage( Colors.of("&7/am addons list &e- list all AM-loaded addons"), - Colors.of("&7/am addons reload &e- reload a single addon"), - Colors.of("&7/am addons info &e- show addon metadata") + Colors.of("&7/am addons reload &e- reload a single loaded addon"), + Colors.of("&7/am addons info &e- show addon metadata"), + Colors.of("&7/am addons load &e- load a new addon dropped at runtime"), + Colors.of("&7/am addons rescan &e- scan addons/ and load any new jars") ); } @@ -33,10 +42,12 @@ public void execute(CommandSender sender, String[] args) { } switch (args[0].toLowerCase()) { - case "list" -> list(sender, am); - case "reload" -> reload(sender, am, args); - case "info" -> info(sender, am, args); - default -> sender.sendMessage(getUsage()); + case "list" -> list(sender, am); + case "reload" -> reload(sender, am, args); + case "info" -> info(sender, am, args); + case "load" -> load(sender, am, args); + case "rescan" -> rescan(sender, am); + default -> sender.sendMessage(getUsage()); } } @@ -115,4 +126,83 @@ private void info(CommandSender sender, AddonManager am, String[] args) { sender.sendMessage(Colors.of("&7 error: &c" + la.getError().getMessage())); } } + + private void load(CommandSender sender, AddonManager am, String[] args) { + if (args.length < 2) { + sender.sendMessage(Colors.of("&cUsage: /am addons load ")); + return; + } + String name = args[1]; + var result = am.loadOne(name); + if (result.isEmpty()) { + sender.sendMessage(Colors.of("&cNo unloaded addon named '" + name + "' found in addons/. " + + "Check the jar is in plugins/AbstractMenus/addons/ and addon.conf names it correctly.")); + return; + } + LoadedAddon la = result.get(); + if (la.getStatus() == AddonStatus.ENABLED) { + sender.sendMessage(Colors.of("&aLoaded " + la.getConf().name() + + " v" + la.getConf().version() + ".")); + } else { + sender.sendMessage(Colors.of("&cLoad failed: " + + (la.getError() == null ? "unknown error" : la.getError().getMessage()))); + } + } + + private void rescan(CommandSender sender, AddonManager am) { + var newlyLoaded = am.rescan(); + if (newlyLoaded.isEmpty()) { + sender.sendMessage(Colors.of("&7No new addons found.")); + return; + } + long enabled = newlyLoaded.stream() + .filter(la -> la.getStatus() == AddonStatus.ENABLED).count(); + long failed = newlyLoaded.size() - enabled; + sender.sendMessage(Colors.of("&aRescan: " + enabled + " loaded" + + (failed > 0 ? ", &c" + failed + " failed" : "") + "&a.")); + for (LoadedAddon la : newlyLoaded) { + String color = la.getStatus() == AddonStatus.ENABLED ? "&a" : "&c"; + sender.sendMessage(Colors.of(color + " " + la.getConf().name() + + " &8v" + la.getConf().version() + + " &7[" + la.getStatus() + "]")); + } + } + + @Override + public List tabComplete(CommandSender sender, String[] args) { + if (args.length == 0) return Collections.emptyList(); + + if (args.length == 1) { + String prefix = args[0].toLowerCase(); + List result = new ArrayList<>(); + for (String s : SUBCOMMANDS) { + if (s.startsWith(prefix)) result.add(s); + } + return result; + } + + if (args.length == 2) { + AddonManager am = AbstractMenus.instance().getAddonManager(); + if (am == null) return Collections.emptyList(); + String prefix = args[1].toLowerCase(); + return switch (args[0].toLowerCase()) { + case "reload", "info" -> filterByPrefix( + am.loaded().stream().map(la -> la.getConf().name()).toList(), + prefix); + case "load" -> filterByPrefix(am.availableNotLoaded(), prefix); + default -> Collections.emptyList(); + }; + } + + return Collections.emptyList(); + } + + private static List filterByPrefix(List source, String prefix) { + List result = new ArrayList<>(); + for (String s : source) { + if (s.toLowerCase().startsWith(prefix)) result.add(s); + } + Collections.sort(result); + return result; + } } From d79a879d29473b53c1ce3b215d963c0be9d19b9b Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Tue, 28 Apr 2026 11:20:51 +0500 Subject: [PATCH 67/71] fix: defer initial menu load to ServerLoadEvent Path 1 addons (plugin-as-addon style, e.g. PlayerPointsAddon on the as-plugin branch) declare `depend: [AbstractMenus, ...]` in their plugin.yml and Bukkit only enables them AFTER AbstractMenus' own onEnable returns. If AM loaded menus inline within onEnable, HOCON parse-time validation of provider:per-action references contributed by those addons would fail with 'Unknown economy provider X' even though the addon would register X seconds later. Move the initial loadMenus() call out of onEnable and into a ServerLoadEvent listener. The event fires after every plugin's onEnable has completed (both at server startup and on /reload), so by the time we parse menus all Path 1 contributions are present. Path 2 addons (jars in plugins/AbstractMenus/addons/) are unaffected - AddonManager.loadAll() still runs inline within AM's onEnable, before the listener is even registered. Listener self-unregisters after firing once so any future hypothetical re-fire of ServerLoadEvent does not double-load menus mid-session. --- .../java/ru/abstractmenus/AbstractMenus.java | 9 ++++- .../listeners/ServerLoadListener.java | 36 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 plugin/src/main/java/ru/abstractmenus/listeners/ServerLoadListener.java diff --git a/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java b/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java index 9828956..9d70629 100644 --- a/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java +++ b/plugin/src/main/java/ru/abstractmenus/AbstractMenus.java @@ -35,6 +35,7 @@ import ru.abstractmenus.listeners.ChatListener; import ru.abstractmenus.listeners.InventoryListener; import ru.abstractmenus.listeners.PlayerListener; +import ru.abstractmenus.listeners.ServerLoadListener; import ru.abstractmenus.listeners.wg.WGHandlers; import ru.abstractmenus.nms.actionbar.ActionBar; import ru.abstractmenus.nms.title.Title; @@ -154,7 +155,13 @@ public void onEnable() { this.addonManager = new AddonManager(this, api); addonManager.loadAll(); - loadMenus(); + // Menus parse via ServerLoadEvent (after every plugin's onEnable + // has completed) so Path 1 plugin-as-addons - which Bukkit only + // enables AFTER us due to depend: AbstractMenus - have a chance to + // register their providers before HOCON parse-time validation + // runs. Path 2 addons are already loaded above, so they work + // either way. + getServer().getPluginManager().registerEvents(new ServerLoadListener(), this); getServer().getPluginManager().registerEvents(new InventoryListener(), this); getServer().getPluginManager().registerEvents(new ProfileStorage(), this); diff --git a/plugin/src/main/java/ru/abstractmenus/listeners/ServerLoadListener.java b/plugin/src/main/java/ru/abstractmenus/listeners/ServerLoadListener.java new file mode 100644 index 0000000..48892b8 --- /dev/null +++ b/plugin/src/main/java/ru/abstractmenus/listeners/ServerLoadListener.java @@ -0,0 +1,36 @@ +package ru.abstractmenus.listeners; + +import org.bukkit.event.EventHandler; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.server.ServerLoadEvent; +import ru.abstractmenus.AbstractMenus; + +/** + * Defers menu loading until every plugin has finished {@code onEnable}. + * + *

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

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

The listener unregisters itself after firing once so a hypothetical + * second event (Bukkit reserves the right) does not double-load menus + * mid-session. + */ +public final class ServerLoadListener implements Listener { + + @EventHandler + public void onServerLoad(ServerLoadEvent event) { + AbstractMenus.instance().loadMenus(); + HandlerList.unregisterAll(this); + } +} From cd5b39be5aaeb035dd2fcb8e291d64fa830630e1 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Tue, 28 Apr 2026 11:28:19 +0500 Subject: [PATCH 68/71] build: rename plugin thin jar to AbstractMenus--thin.jar Default Gradle naming was producing 'plugin-.jar' for the regular jar task because the subproject directory is named 'plugin'. That name is misleading on disk - server admins encountering it have no hint that it's part of AbstractMenus. Set base.archivesName = 'AbstractMenus' on the :plugin module so both the thin jar and the shadowJar share the canonical project name. Add a 'thin' classifier on the regular jar task so it cannot be confused with the user-facing fat jar: plugin/build/libs/ AbstractMenus-2.0.0-alpha.jar <- shaded, ships to plugins/ AbstractMenus-2.0.0-alpha-thin.jar <- dev-only, no bundled deps shadowJar gets archiveClassifier = '' explicitly to drop the default 'all' classifier the shadow plugin would otherwise add. CI workflows (build.yml + release.yml) updated to skip the -thin jar when picking the artifact to publish. Without the filter, ASCII sort order would have selected -thin first ('-' = 0x2D < '.' = 0x2E). --- .github/workflows/build.yml | 3 ++- .github/workflows/release.yml | 3 ++- plugin/build.gradle | 17 ++++++++++++++++- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 08a19dc..add6095 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -51,7 +51,8 @@ jobs: - name: Resolve plugin JAR path id: plugin_jar - run: echo "path=$(ls plugin/build/libs/AbstractMenus-*.jar | head -n1)" >> "$GITHUB_OUTPUT" + # Skip the dev-only -thin jar; only the shaded fat jar ships. + run: echo "path=$(ls plugin/build/libs/AbstractMenus-*.jar | grep -v -- '-thin\.jar$' | head -n1)" >> "$GITHUB_OUTPUT" - name: Resolve API JAR paths id: api_jars diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d299f99..7039c62 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -52,7 +52,8 @@ jobs: - name: Resolve artifact paths id: paths run: | - PLUGIN=$(ls plugin/build/libs/AbstractMenus-*.jar | head -n1) + # Skip the dev-only -thin jar; only the shaded fat jar ships. + PLUGIN=$(ls plugin/build/libs/AbstractMenus-*.jar | grep -v -- '-thin\.jar$' | head -n1) API_BIN=$(ls api/build/libs/api-*.jar | grep -v 'sources\|javadoc' | head -n1) API_SRC=$(ls api/build/libs/api-*-sources.jar | head -n1) API_DOC=$(ls api/build/libs/api-*-javadoc.jar | head -n1) diff --git a/plugin/build.gradle b/plugin/build.gradle index e106d97..9092520 100644 --- a/plugin/build.gradle +++ b/plugin/build.gradle @@ -95,18 +95,33 @@ dependencies { testAnnotationProcessor 'org.projectlombok:lombok:1.18.44' } +// The :plugin module ships as a fat jar named AbstractMenus-.jar. +// Default Gradle naming would produce 'plugin-.jar' (using the +// subproject directory name), which is misleading on disk for ops. +base { + archivesName = 'AbstractMenus' +} + processResources { filesMatching("plugin.yml") { expand([version: project.version]) } } +// Thin jar lands as AbstractMenus--thin.jar so it cannot be confused +// with the user-facing fat jar produced by shadowJar. Devs-only artifact. +jar { + archiveClassifier.set('thin') +} + shadowJar { relocate "com.tcoded.folialib", "ru.abstractmenus.lib.folialib" dependencies { exclude dependency('org.inventivetalent.spiget-update:bukkit') } - archiveFileName.set("AbstractMenus-${project.version}.jar") + // Drop the default 'all' classifier so the fat jar is the canonical + // AbstractMenus-.jar that operators drop into plugins/. + archiveClassifier.set('') } test { From 56d103354d8ea50c43d0faf8f06fa1aa192895c3 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Tue, 28 Apr 2026 11:32:52 +0500 Subject: [PATCH 69/71] build: rename api jar to AbstractMenus-api-.jar locally Local file in api/build/libs/ was 'api-.jar' (default from project.name = 'api'). On CI artifact downloads or manual SCP it was hard to tell at a glance which project the jar belonged to. Set base.archivesName = 'AbstractMenus-api' on :api so the local file is now 'AbstractMenus-api-.jar', visually grouping with the 'AbstractMenus-.jar' shadow jar from :plugin. The Maven publication is unaffected: maven-publish renames artifacts to -.jar inside the repository regardless of the local jar filename, so the Maven coord 'ru.abstractmenus:api:' still resolves correctly. Verified via :api:publishToMavenLocal: ~/.m2/repository/ru/abstractmenus/api/2.0.0-alpha/ still contains api-2.0.0-alpha.{jar,pom,module} as expected. CI workflows updated to grep AbstractMenus-api-*.jar. --- .github/workflows/build.yml | 6 +++--- .github/workflows/release.yml | 6 +++--- api/build.gradle | 12 ++++++++++++ 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index add6095..12f335f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -57,9 +57,9 @@ jobs: - name: Resolve API JAR paths id: api_jars run: | - echo "binary=$(ls api/build/libs/api-*.jar | grep -v 'sources\|javadoc' | head -n1)" >> "$GITHUB_OUTPUT" - echo "sources=$(ls api/build/libs/api-*-sources.jar | head -n1)" >> "$GITHUB_OUTPUT" - echo "javadoc=$(ls api/build/libs/api-*-javadoc.jar | head -n1)" >> "$GITHUB_OUTPUT" + echo "binary=$(ls api/build/libs/AbstractMenus-api-*.jar | grep -v 'sources\|javadoc' | head -n1)" >> "$GITHUB_OUTPUT" + echo "sources=$(ls api/build/libs/AbstractMenus-api-*-sources.jar | head -n1)" >> "$GITHUB_OUTPUT" + echo "javadoc=$(ls api/build/libs/AbstractMenus-api-*-javadoc.jar | head -n1)" >> "$GITHUB_OUTPUT" - name: Upload plugin shadow JAR uses: actions/upload-artifact@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7039c62..b77bde6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -54,9 +54,9 @@ jobs: run: | # Skip the dev-only -thin jar; only the shaded fat jar ships. PLUGIN=$(ls plugin/build/libs/AbstractMenus-*.jar | grep -v -- '-thin\.jar$' | head -n1) - API_BIN=$(ls api/build/libs/api-*.jar | grep -v 'sources\|javadoc' | head -n1) - API_SRC=$(ls api/build/libs/api-*-sources.jar | head -n1) - API_DOC=$(ls api/build/libs/api-*-javadoc.jar | head -n1) + API_BIN=$(ls api/build/libs/AbstractMenus-api-*.jar | grep -v 'sources\|javadoc' | head -n1) + API_SRC=$(ls api/build/libs/AbstractMenus-api-*-sources.jar | head -n1) + API_DOC=$(ls api/build/libs/AbstractMenus-api-*-javadoc.jar | head -n1) echo "plugin=$PLUGIN" >> "$GITHUB_OUTPUT" echo "api_bin=$API_BIN" >> "$GITHUB_OUTPUT" echo "api_src=$API_SRC" >> "$GITHUB_OUTPUT" diff --git a/api/build.gradle b/api/build.gradle index cfca839..43bc7df 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -3,6 +3,18 @@ plugins { id 'maven-publish' } +// Local file in api/build/libs/ is AbstractMenus-api-.jar so +// it visually groups with AbstractMenus-.jar from :plugin and an +// op who copies both off CI artifacts can tell at a glance both are AM. +// +// Maven publish artifactId stays 'api' (project.name) so the coord +// 'ru.abstractmenus:api:' is unchanged for consumers - Gradle's +// maven-publish renames files to -.jar inside the +// repository regardless of what archivesName is on the local jar task. +base { + archivesName = 'AbstractMenus-api' +} + repositories { // root already supplies papermc + jitpack + mavenCentral + mavenLocal } From c7c0560c50e25e4280e9b8344eddc131d2018046 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Tue, 28 Apr 2026 11:35:25 +0500 Subject: [PATCH 70/71] build: aggregate shipping artifacts into /build/dist/ Multi-module layout left release artifacts scattered across two paths: plugin/build/libs/AbstractMenus-.jar (fat shadow) api/build/libs/AbstractMenus-api-.jar (api binary) api/build/libs/AbstractMenus-api--sources.jar api/build/libs/AbstractMenus-api--javadoc.jar For ops, manual SCP, and CI artifact uploads it's nicer to have all four files in one directory. Add a 'dist' Copy task at root that gathers them into /build/dist/ via the per-task archiveFile providers (so the Gradle file resolution stays lazy and incremental caching keeps working). ./gradlew dist # explicit ./gradlew build # implicit (auto-finalizer on root build) Per-module build/libs/ paths remain unchanged so existing CI grep patterns and devs' muscle memory keep working - dist is purely additive, not a replacement. Root needs the 'base' plugin to have its own 'build' lifecycle task (otherwise tasks.named('build') has nothing to attach to). --- build.gradle | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/build.gradle b/build.gradle index 7fdcbb7..61ccf21 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,12 @@ // Common configuration applied to every subproject. // Subproject-specific plugins (java-library, shadow, paperweight) stay in subprojects. +plugins { + // Root needs the base lifecycle ('build', 'assemble', 'clean') so we + // can hook the dist aggregator below into it. + id 'base' +} + allprojects { group = 'ru.abstractmenus' version = '2.0.0-alpha' @@ -28,3 +34,49 @@ subprojects { useJUnitPlatform() } } + +// ----------------------------------------------------------------------- +// dist - aggregate every shipping artifact into /build/dist/ +// +// Mirrors the user-facing files in one place so a server admin (or CI) +// can grab everything with a single cp / scp: +// +// build/dist/ +// AbstractMenus-.jar (plugin, fat shadow) +// AbstractMenus-api-.jar (api binary, Maven coord +// ru.abstractmenus:api:) +// AbstractMenus-api--sources.jar +// AbstractMenus-api--javadoc.jar +// +// The per-module build/libs/ directories are unchanged - existing CI +// paths and Gradle's incremental cache keep working. +// +// ./gradlew build automatically runs dist as a finalizer, so a normal +// build leaves everything in build/dist/. Run ./gradlew dist alone if +// you only need to refresh the aggregate (skips tests). +// ----------------------------------------------------------------------- +tasks.register('dist', Copy) { + description = 'Aggregates shipping artifacts into build/dist/.' + group = 'distribution' + + def pluginShadow = project(':plugin').tasks.named('shadowJar') + def apiJar = project(':api').tasks.named('jar') + def apiSources = project(':api').tasks.named('sourcesJar') + def apiJavadoc = project(':api').tasks.named('javadocJar') + + dependsOn pluginShadow, apiJar, apiSources, apiJavadoc + + from(pluginShadow.flatMap { it.archiveFile }) + from(apiJar.flatMap { it.archiveFile }) + from(apiSources.flatMap { it.archiveFile }) + from(apiJavadoc.flatMap { it.archiveFile }) + + into layout.buildDirectory.dir('dist') +} + +// Hook dist into the root 'build' lifecycle so plain `./gradlew build` +// produces the aggregate. finalizedBy keeps it ordered correctly even if +// some upstream task fails partway through. +tasks.named('build').configure { + finalizedBy('dist') +} From 68a1c5d0252be17e7e32354e145e77dabad07854 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Tue, 28 Apr 2026 11:40:15 +0500 Subject: [PATCH 71/71] build: also trigger dist on shadowJar and assemble build/dist/ was only refreshed via './gradlew build'. Devs running the fast dev-loop command './gradlew :plugin:shadowJar' got the new jar in plugin/build/libs/ but had to remember to also run :dist (or build) before scp/cp. The two paths drifted. Hook dist as a finalizer on: - :plugin:shadowJar (dev-loop fast path) - assemble (all artifacts, no tests) - build (already; kept for clarity) dist is a Copy task with proper input/output declarations so finalizing it three times in a single invocation does no extra work; Gradle's incremental build skips when sources are up-to-date. --- build.gradle | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index 61ccf21..997e5f5 100644 --- a/build.gradle +++ b/build.gradle @@ -74,9 +74,23 @@ tasks.register('dist', Copy) { into layout.buildDirectory.dir('dist') } -// Hook dist into the root 'build' lifecycle so plain `./gradlew build` -// produces the aggregate. finalizedBy keeps it ordered correctly even if -// some upstream task fails partway through. -tasks.named('build').configure { - finalizedBy('dist') +// Hook dist into every common dev-cycle entry point so the aggregate in +// build/dist/ is always fresh, regardless of which command the dev ran. +// +// ./gradlew shadowJar -> finalizes :dist (fast loop "fix -> server") +// ./gradlew assemble -> finalizes :dist (all artifacts, no tests) +// ./gradlew build -> finalizes :dist (artifacts + tests) +// ./gradlew dist -> dist itself +// +// finalizedBy is correct here (vs dependsOn) because we want dist to run +// AFTER the upstream task succeeds, not be pulled in as its dependency. +tasks.named('build').configure { finalizedBy('dist') } +tasks.named('assemble').configure { finalizedBy('dist') } + +// :plugin:shadowJar is the hot-path command during plugin development; +// hook it directly so the dev-loop jar lands in build/dist/ too. +project(':plugin').afterEvaluate { + project(':plugin').tasks.named('shadowJar').configure { + finalizedBy(':dist') + } }